|
1 | 1 | /* |
2 | | - * Copyright (C) 2024 pedroSG94. |
| 2 | + * Copyright (C) 2026 pedroSG94. |
3 | 3 | * |
4 | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | 5 | * you may not use this file except in compliance with the License. |
@@ -37,79 +37,68 @@ import java.nio.ByteBuffer |
37 | 37 | import kotlin.concurrent.Volatile |
38 | 38 | import kotlin.math.max |
39 | 39 |
|
40 | | -abstract class BaseRecordController : RecordController { |
| 40 | +/** |
| 41 | + * Record async to avoid block the thread used to send frames to protocol module. |
| 42 | + */ |
| 43 | +abstract class AsyncBaseRecordController : RecordController { |
41 | 44 |
|
42 | 45 | companion object { |
43 | | - const val TAG: String = "RecordController" |
| 46 | + const val TAG: String = "AsyncRecordController" |
| 47 | + private const val CAPACITY = 500 |
44 | 48 | } |
45 | 49 |
|
46 | | - @JvmField |
47 | 50 | @Volatile |
48 | | - protected var status: RecordController.Status = RecordController.Status.STOPPED |
49 | | - @JvmField |
50 | | - protected var videoCodec: VideoCodec = VideoCodec.H264 |
51 | | - @JvmField |
52 | | - protected var audioCodec: AudioCodec = AudioCodec.AAC |
53 | | - @JvmField |
54 | | - protected var pauseMoment: Long = 0 |
55 | | - @JvmField |
56 | | - protected var pauseTime: Long = 0 |
57 | | - @JvmField |
| 51 | + protected var recordStatus: RecordController.Status = RecordController.Status.STOPPED |
58 | 52 | protected var listener: RecordController.Listener? = null |
59 | | - @JvmField |
60 | | - protected var videoTrack: Int = -1 |
61 | | - @JvmField |
62 | | - protected var audioTrack: Int = -1 |
63 | | - @JvmField |
64 | 53 | protected var bitrateManager: BitrateManager? = null |
65 | | - @JvmField |
66 | | - @Volatile |
67 | | - protected var startTs: Long = 0 |
68 | | - @JvmField |
69 | 54 | protected var tracks: RecordTracks = RecordTracks.ALL |
70 | 55 | protected var myRequestKeyFrame: RequestKeyFrame? = null |
| 56 | + private var videoCodec: VideoCodec = VideoCodec.H264 |
| 57 | + private var audioCodec: AudioCodec = AudioCodec.AAC |
| 58 | + private var pauseMoment: Long = 0 |
| 59 | + private var pauseTime: Long = 0 |
| 60 | + @Volatile |
| 61 | + private var startTs: Long = 0 |
71 | 62 | private val scope = CoroutineScope(Dispatchers.IO) |
72 | 63 | private var muxerChannel: Channel<MediaFrame>? = null |
73 | 64 | private var muxerJob: Job? = null |
74 | 65 |
|
75 | | - fun getStatus() = status |
76 | | - |
77 | | - fun setRequestKeyFrame(requestKeyFrame: RequestKeyFrame?) { |
| 66 | + override fun setRequestKeyFrame(requestKeyFrame: RequestKeyFrame?) { |
78 | 67 | this.myRequestKeyFrame = requestKeyFrame |
79 | 68 | } |
80 | 69 |
|
81 | | - fun updateInfo(recordController: BaseRecordController) { |
82 | | - videoCodec = recordController.videoCodec |
83 | | - audioCodec = recordController.audioCodec |
| 70 | + override fun updateInfo(videoCodec: VideoCodec, audioCodec: AudioCodec) { |
| 71 | + this.videoCodec = videoCodec |
| 72 | + this.audioCodec = audioCodec |
84 | 73 | } |
85 | 74 |
|
86 | | - fun setVideoCodec(videoCodec: VideoCodec) { |
| 75 | + override fun setVideoCodec(videoCodec: VideoCodec) { |
87 | 76 | this.videoCodec = videoCodec |
88 | 77 | } |
89 | 78 |
|
90 | | - fun setAudioCodec(audioCodec: AudioCodec) { |
| 79 | + override fun setAudioCodec(audioCodec: AudioCodec) { |
91 | 80 | this.audioCodec = audioCodec |
92 | 81 | } |
93 | 82 |
|
94 | | - val isRunning: Boolean |
95 | | - get() = status == RecordController.Status.STARTED || status == RecordController.Status.RECORDING || status == RecordController.Status.RESUMED || status == RecordController.Status.PAUSED |
| 83 | + override fun getVideoCodec(): VideoCodec = videoCodec |
| 84 | + override fun getAudioCodec(): AudioCodec = audioCodec |
| 85 | + override fun isRunning(): Boolean = recordStatus == RecordController.Status.STARTED || recordStatus == RecordController.Status.RECORDING || recordStatus == RecordController.Status.RESUMED || recordStatus == RecordController.Status.PAUSED |
| 86 | + override fun isRecording(): Boolean = recordStatus == RecordController.Status.RECORDING |
| 87 | + override fun getStatus(): RecordController.Status = recordStatus |
96 | 88 |
|
97 | | - val isRecording: Boolean |
98 | | - get() = status == RecordController.Status.RECORDING |
99 | | - |
100 | | - fun pauseRecord() { |
101 | | - if (status == RecordController.Status.RECORDING) { |
| 89 | + override fun pauseRecord() { |
| 90 | + if (recordStatus == RecordController.Status.RECORDING) { |
102 | 91 | pauseMoment = getCurrentTimeMicro() |
103 | | - status = RecordController.Status.PAUSED |
104 | | - listener?.onStatusChange(status) |
| 92 | + recordStatus = RecordController.Status.PAUSED |
| 93 | + listener?.onStatusChange(recordStatus) |
105 | 94 | } |
106 | 95 | } |
107 | 96 |
|
108 | | - fun resumeRecord() { |
109 | | - if (status == RecordController.Status.PAUSED) { |
| 97 | + override fun resumeRecord() { |
| 98 | + if (recordStatus == RecordController.Status.PAUSED) { |
110 | 99 | pauseTime += getCurrentTimeMicro() - pauseMoment |
111 | | - status = RecordController.Status.RESUMED |
112 | | - listener?.onStatusChange(status) |
| 100 | + recordStatus = RecordController.Status.RESUMED |
| 101 | + listener?.onStatusChange(recordStatus) |
113 | 102 | } |
114 | 103 | } |
115 | 104 |
|
@@ -156,34 +145,60 @@ abstract class BaseRecordController : RecordController { |
156 | 145 | listener: RecordController.Listener?, |
157 | 146 | tracks: RecordTracks |
158 | 147 | ) { |
159 | | - muxerChannel = Channel(Channel.UNLIMITED) |
160 | | - muxerJob = scope.launch { |
161 | | - val channel = muxerChannel ?: return@launch |
162 | | - for (frame in channel) { |
163 | | - onWriteFrame(frame) |
164 | | - } |
| 148 | + start(listener, tracks) |
| 149 | + try { |
| 150 | + startRecordImp(fd, listener, tracks) |
| 151 | + } catch (e: Exception) { |
| 152 | + stopRecord() |
| 153 | + throw e |
165 | 154 | } |
166 | | - startRecordImp(fd, listener, tracks) |
167 | 155 | } |
168 | 156 |
|
169 | 157 | override fun startRecord( |
170 | 158 | path: String, |
171 | 159 | listener: RecordController.Listener?, |
172 | 160 | tracks: RecordTracks |
173 | 161 | ) { |
174 | | - muxerChannel = Channel(Channel.UNLIMITED) |
| 162 | + start(listener, tracks) |
| 163 | + try { |
| 164 | + startRecordImp(path, listener, tracks) |
| 165 | + } catch (e: Exception) { |
| 166 | + stopRecord() |
| 167 | + throw e |
| 168 | + } |
| 169 | + } |
| 170 | + |
| 171 | + private fun start( |
| 172 | + listener: RecordController.Listener?, |
| 173 | + tracks: RecordTracks |
| 174 | + ) { |
| 175 | + muxerChannel = Channel(CAPACITY) |
175 | 176 | muxerJob = scope.launch { |
176 | 177 | val channel = muxerChannel ?: return@launch |
177 | 178 | for (frame in channel) onWriteFrame(frame) |
178 | 179 | } |
179 | | - startRecordImp(path, listener, tracks) |
| 180 | + this.tracks = tracks |
| 181 | + this.listener = listener |
| 182 | + recordStatus = RecordController.Status.STARTED |
| 183 | + if (listener != null) { |
| 184 | + bitrateManager = BitrateManager(listener) |
| 185 | + listener.onStatusChange(recordStatus) |
| 186 | + } else { |
| 187 | + bitrateManager = null |
| 188 | + } |
180 | 189 | } |
181 | 190 |
|
182 | 191 | override fun stopRecord() { |
183 | 192 | muxerChannel?.close() |
184 | 193 | muxerChannel = null |
185 | 194 | muxerJob?.cancel() |
186 | 195 | runBlocking { muxerJob?.join() } |
| 196 | + recordStatus = RecordController.Status.STOPPED |
| 197 | + pauseMoment = 0 |
| 198 | + pauseTime = 0 |
| 199 | + startTs = 0 |
| 200 | + myRequestKeyFrame = null |
| 201 | + listener?.onStatusChange(recordStatus) |
187 | 202 | stopRecordImp() |
188 | 203 | } |
189 | 204 |
|
|
0 commit comments