@stream-io/video-react-native-sdk 1.37.1-beta.0 → 1.38.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/android/src/main/java/com/streamvideo/reactnative/StreamVideoReactNativeModule.kt +0 -81
- package/dist/commonjs/hooks/index.js +0 -11
- package/dist/commonjs/hooks/index.js.map +1 -1
- package/dist/commonjs/modules/call-manager/CallManager.js +13 -0
- package/dist/commonjs/modules/call-manager/CallManager.js.map +1 -1
- package/dist/commonjs/providers/StreamCall/AudioInterruptionTracer.js +37 -0
- package/dist/commonjs/providers/StreamCall/AudioInterruptionTracer.js.map +1 -0
- package/dist/commonjs/providers/StreamCall/index.js +2 -1
- package/dist/commonjs/providers/StreamCall/index.js.map +1 -1
- package/dist/commonjs/utils/internal/callingx/callingx.js +2 -2
- package/dist/commonjs/utils/internal/callingx/callingx.js.map +1 -1
- package/dist/commonjs/utils/internal/registerSDKGlobals.js +16 -0
- package/dist/commonjs/utils/internal/registerSDKGlobals.js.map +1 -1
- package/dist/commonjs/utils/push/internal/ios.js +5 -0
- package/dist/commonjs/utils/push/internal/ios.js.map +1 -1
- package/dist/commonjs/version.js +1 -1
- package/dist/commonjs/version.js.map +1 -1
- package/dist/module/hooks/index.js +0 -1
- package/dist/module/hooks/index.js.map +1 -1
- package/dist/module/modules/call-manager/CallManager.js +13 -0
- package/dist/module/modules/call-manager/CallManager.js.map +1 -1
- package/dist/module/providers/StreamCall/AudioInterruptionTracer.js +30 -0
- package/dist/module/providers/StreamCall/AudioInterruptionTracer.js.map +1 -0
- package/dist/module/providers/StreamCall/index.js +2 -1
- package/dist/module/providers/StreamCall/index.js.map +1 -1
- package/dist/module/utils/internal/callingx/callingx.js +2 -2
- package/dist/module/utils/internal/callingx/callingx.js.map +1 -1
- package/dist/module/utils/internal/registerSDKGlobals.js +17 -1
- package/dist/module/utils/internal/registerSDKGlobals.js.map +1 -1
- package/dist/module/utils/push/internal/ios.js +5 -0
- package/dist/module/utils/push/internal/ios.js.map +1 -1
- package/dist/module/version.js +1 -1
- package/dist/module/version.js.map +1 -1
- package/dist/typescript/hooks/index.d.ts +0 -1
- package/dist/typescript/hooks/index.d.ts.map +1 -1
- package/dist/typescript/modules/call-manager/CallManager.d.ts +8 -1
- package/dist/typescript/modules/call-manager/CallManager.d.ts.map +1 -1
- package/dist/typescript/modules/call-manager/types.d.ts +6 -0
- package/dist/typescript/modules/call-manager/types.d.ts.map +1 -1
- package/dist/typescript/providers/StreamCall/AudioInterruptionTracer.d.ts +5 -0
- package/dist/typescript/providers/StreamCall/AudioInterruptionTracer.d.ts.map +1 -0
- package/dist/typescript/providers/StreamCall/index.d.ts.map +1 -1
- package/dist/typescript/utils/internal/registerSDKGlobals.d.ts.map +1 -1
- package/dist/typescript/utils/push/internal/ios.d.ts.map +1 -1
- package/dist/typescript/version.d.ts +1 -1
- package/dist/typescript/version.d.ts.map +1 -1
- package/ios/PictureInPicture/PictureInPictureAvatarView.swift +3 -1
- package/ios/PictureInPicture/StreamBufferTransformer.swift +13 -4
- package/ios/PictureInPicture/StreamPictureInPictureVideoRenderer.swift +79 -71
- package/ios/PictureInPicture/StreamRTCYUVBuffer.swift +20 -16
- package/ios/StreamInCallManager.swift +256 -81
- package/ios/StreamVideoReactNative-Bridging-Header.h +0 -2
- package/ios/StreamVideoReactNative.m +0 -81
- package/package.json +11 -11
- package/src/hooks/index.ts +0 -1
- package/src/modules/call-manager/CallManager.ts +25 -1
- package/src/modules/call-manager/types.ts +7 -0
- package/src/providers/StreamCall/AudioInterruptionTracer.tsx +51 -0
- package/src/providers/StreamCall/index.tsx +2 -0
- package/src/utils/internal/callingx/callingx.ts +2 -2
- package/src/utils/internal/registerSDKGlobals.ts +23 -1
- package/src/utils/push/internal/ios.ts +5 -0
- package/src/version.ts +1 -1
- package/android/src/main/java/com/streamvideo/reactnative/recorder/AudioPipeline.kt +0 -436
- package/android/src/main/java/com/streamvideo/reactnative/recorder/EncoderConstants.kt +0 -17
- package/android/src/main/java/com/streamvideo/reactnative/recorder/PipelineHost.kt +0 -36
- package/android/src/main/java/com/streamvideo/reactnative/recorder/RecorderPlaybackSamplesSink.kt +0 -60
- package/android/src/main/java/com/streamvideo/reactnative/recorder/RecorderVideoSink.kt +0 -31
- package/android/src/main/java/com/streamvideo/reactnative/recorder/TracksRecorderManager.kt +0 -329
- package/android/src/main/java/com/streamvideo/reactnative/recorder/VideoPipeline.kt +0 -472
- package/dist/commonjs/hooks/useLoopbackRecording.js +0 -243
- package/dist/commonjs/hooks/useLoopbackRecording.js.map +0 -1
- package/dist/module/hooks/useLoopbackRecording.js +0 -238
- package/dist/module/hooks/useLoopbackRecording.js.map +0 -1
- package/dist/typescript/hooks/useLoopbackRecording.d.ts +0 -85
- package/dist/typescript/hooks/useLoopbackRecording.d.ts.map +0 -1
- package/ios/TracksRecorder/AudioPipeline.swift +0 -270
- package/ios/TracksRecorder/PipelineHost.swift +0 -56
- package/ios/TracksRecorder/RecorderAudioRenderTap.swift +0 -154
- package/ios/TracksRecorder/RecorderVideoSink.swift +0 -137
- package/ios/TracksRecorder/TracksRecorderManager.swift +0 -327
- package/ios/TracksRecorder/VideoPipeline.swift +0 -297
- package/src/hooks/useLoopbackRecording.ts +0 -438
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
package com.streamvideo.reactnative.recorder
|
|
2
|
-
|
|
3
|
-
import org.webrtc.VideoFrame
|
|
4
|
-
import org.webrtc.VideoSink
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Per-track [VideoSink] for [TracksRecorderManager]. Stays dumb —
|
|
8
|
-
* retains each incoming frame and forwards it; the manager handles
|
|
9
|
-
* queue hops, I420 → NV12 conversion, and the encoder feed.
|
|
10
|
-
*
|
|
11
|
-
* `onFrame` is invoked from a WebRTC delivery thread. **Never** call
|
|
12
|
-
* `removeSink` from inside `onFrame` — Android WebRTC has a known
|
|
13
|
-
* deadlock there. The manager removes from its handler thread.
|
|
14
|
-
*/
|
|
15
|
-
internal class RecorderVideoSink(
|
|
16
|
-
private val handler: (VideoFrame) -> Unit,
|
|
17
|
-
) : VideoSink {
|
|
18
|
-
|
|
19
|
-
override fun onFrame(frame: VideoFrame) {
|
|
20
|
-
// Retain so the buffer outlives this delivery-thread call.
|
|
21
|
-
// The manager releases after the encoder consumes it.
|
|
22
|
-
frame.retain()
|
|
23
|
-
try {
|
|
24
|
-
handler.invoke(frame)
|
|
25
|
-
} catch (t: Throwable) {
|
|
26
|
-
// Balance the retain when the manager rejects the frame.
|
|
27
|
-
frame.release()
|
|
28
|
-
throw t
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
@@ -1,329 +0,0 @@
|
|
|
1
|
-
package com.streamvideo.reactnative.recorder
|
|
2
|
-
|
|
3
|
-
import android.content.Context
|
|
4
|
-
import android.media.MediaMuxer
|
|
5
|
-
import android.os.Handler
|
|
6
|
-
import android.os.HandlerThread
|
|
7
|
-
import android.os.Looper
|
|
8
|
-
import android.util.Log
|
|
9
|
-
import com.oney.WebRTCModule.WebRTCModule
|
|
10
|
-
import java.io.File
|
|
11
|
-
import org.webrtc.VideoTrack
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Orchestrator for the React Native track recorder. Owns the [MediaMuxer], the recording lifecycle,
|
|
15
|
-
* the muxer-start gate, and the terminal-completion barrier. Delegates the encoder + sink + drain
|
|
16
|
-
* work to [VideoPipeline] and [AudioPipeline] respectively (composed via [PipelineHost]).
|
|
17
|
-
*
|
|
18
|
-
* The public surface is wrapped by `StreamVideoReactNativeModule.kt`'s `startTrackRecording` /
|
|
19
|
-
* `stopTrackRecording` / `clearStreamRecordings` / `getStreamRecordings` methods, which in turn are
|
|
20
|
-
* the bridge contract used by the JS `useLoopbackRecording` hook. Knows nothing about loopback or
|
|
21
|
-
* any specific recording use case — it's a generic encode-and-mux orchestrator.
|
|
22
|
-
*
|
|
23
|
-
* Both video and audio are optional. Audio is always requested by the current bridge contract;
|
|
24
|
-
* video is requested whenever the caller passes a `videoTrackId` that resolves to a [VideoTrack].
|
|
25
|
-
* The [MediaMuxer] only starts once **all** active pipelines have reported their track (gated by
|
|
26
|
-
* [maybeStartMuxer] on [pendingPipelines]).
|
|
27
|
-
*
|
|
28
|
-
* Threading: a dedicated [HandlerThread] serialises every state mutation (start, stop, encoder
|
|
29
|
-
* feed, muxer writes) so the rest of the file is lock-free. Pipelines accept buffers on WebRTC
|
|
30
|
-
* delivery threads and post to this handler before touching any state.
|
|
31
|
-
*
|
|
32
|
-
* Completion semantics: `startRecording` is the **lifecycle promise** — it fires once at the
|
|
33
|
-
* recording's terminal moment with the produced file (or an error). `stopRecording` is a void sync
|
|
34
|
-
* point that resolves after native finalisation, so callers can `await stopTrackRecording(); await
|
|
35
|
-
* getStreamRecordings()` without racing the disk flush. Same shape as iOS.
|
|
36
|
-
*/
|
|
37
|
-
class TracksRecorderManager private constructor() : PipelineHost {
|
|
38
|
-
|
|
39
|
-
companion object {
|
|
40
|
-
@JvmField val shared = TracksRecorderManager()
|
|
41
|
-
|
|
42
|
-
private const val TAG = "TracksRecorder"
|
|
43
|
-
private const val RECORDINGS_DIR_NAME = "StreamRecordings"
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
private val thread = HandlerThread("io.stream.video.tracks-recorder").apply { start() }
|
|
47
|
-
private val timerHandler = Handler(Looper.getMainLooper())
|
|
48
|
-
|
|
49
|
-
override val handler = Handler(thread.looper)
|
|
50
|
-
override var muxer: MediaMuxer? = null
|
|
51
|
-
private set
|
|
52
|
-
override var muxerStarted = false
|
|
53
|
-
private set
|
|
54
|
-
|
|
55
|
-
private var videoPipeline: VideoPipeline? = null
|
|
56
|
-
private var audioPipeline: AudioPipeline? = null
|
|
57
|
-
|
|
58
|
-
private var outputFile: File? = null
|
|
59
|
-
|
|
60
|
-
private var recordingCompletion: ((File?, Throwable?) -> Unit)? = null
|
|
61
|
-
private var isCompleted = false
|
|
62
|
-
private var isRecording = false
|
|
63
|
-
|
|
64
|
-
private var pendingPipelines = 0
|
|
65
|
-
private var recordingStartHostTimeNs: Long? = null
|
|
66
|
-
private var autoStopRunnable: Runnable? = null
|
|
67
|
-
|
|
68
|
-
fun recordingsDirectory(context: Context): File {
|
|
69
|
-
val dir = File(context.cacheDir, RECORDINGS_DIR_NAME)
|
|
70
|
-
if (!dir.exists()) dir.mkdirs()
|
|
71
|
-
return dir
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
fun startRecording(
|
|
75
|
-
context: Context,
|
|
76
|
-
webRTCModule: WebRTCModule,
|
|
77
|
-
videoTrackId: String?,
|
|
78
|
-
maxDurationMs: Long,
|
|
79
|
-
targetWidth: Int = 0,
|
|
80
|
-
targetHeight: Int = 0,
|
|
81
|
-
completion: (File?, Throwable?) -> Unit,
|
|
82
|
-
) {
|
|
83
|
-
handler.post {
|
|
84
|
-
if (isRecording) {
|
|
85
|
-
completion(null, RecordingError("recording_in_progress"))
|
|
86
|
-
return@post
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
val resolvedVideoTrack =
|
|
90
|
-
videoTrackId?.let { webRTCModule.getTrackById(it) } as? VideoTrack
|
|
91
|
-
|
|
92
|
-
val dir = recordingsDirectory(context)
|
|
93
|
-
val outFile = File(dir, "recording_${System.currentTimeMillis()}.mp4")
|
|
94
|
-
val muxerInstance: MediaMuxer =
|
|
95
|
-
try {
|
|
96
|
-
MediaMuxer(
|
|
97
|
-
outFile.absolutePath,
|
|
98
|
-
MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4
|
|
99
|
-
)
|
|
100
|
-
} catch (t: Throwable) {
|
|
101
|
-
completion(null, t)
|
|
102
|
-
return@post
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
resetTransientState()
|
|
106
|
-
|
|
107
|
-
this.muxer = muxerInstance
|
|
108
|
-
this.outputFile = outFile
|
|
109
|
-
this.recordingCompletion = completion
|
|
110
|
-
this.isRecording = true
|
|
111
|
-
|
|
112
|
-
if (resolvedVideoTrack != null) {
|
|
113
|
-
val pipeline = VideoPipeline(
|
|
114
|
-
host = this,
|
|
115
|
-
videoTrack = resolvedVideoTrack,
|
|
116
|
-
targetWidth = targetWidth,
|
|
117
|
-
targetHeight = targetHeight,
|
|
118
|
-
)
|
|
119
|
-
videoPipeline = pipeline
|
|
120
|
-
pendingPipelines++
|
|
121
|
-
pipeline.start()
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
val audio = AudioPipeline(host = this, webRTCModule = webRTCModule)
|
|
125
|
-
audioPipeline = audio
|
|
126
|
-
pendingPipelines++
|
|
127
|
-
audio.start()
|
|
128
|
-
|
|
129
|
-
if (maxDurationMs > 0) {
|
|
130
|
-
val runnable = Runnable { stopRecording { /* fire-and-forget */} }
|
|
131
|
-
autoStopRunnable = runnable
|
|
132
|
-
timerHandler.postDelayed(runnable, maxDurationMs)
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
Log.i(
|
|
136
|
-
TAG,
|
|
137
|
-
"recording started video=${resolvedVideoTrack != null} audio=true → ${outFile.absolutePath}",
|
|
138
|
-
)
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
fun stopRecording(completion: () -> Unit) {
|
|
143
|
-
// Detach sinks synchronously off the recorder handler so no
|
|
144
|
-
// new buffers can be enqueued while the backlog drains. The
|
|
145
|
-
// audio pipeline also restores speaker volume here so the
|
|
146
|
-
// mute lifts immediately.
|
|
147
|
-
videoPipeline?.detachSink()
|
|
148
|
-
audioPipeline?.detachSink()
|
|
149
|
-
autoStopRunnable?.let { timerHandler.removeCallbacks(it) }
|
|
150
|
-
autoStopRunnable = null
|
|
151
|
-
|
|
152
|
-
handler.post {
|
|
153
|
-
if (!isRecording) {
|
|
154
|
-
completion()
|
|
155
|
-
return@post
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
val video = videoPipeline
|
|
159
|
-
val audio = audioPipeline
|
|
160
|
-
val muxerInstance = muxer
|
|
161
|
-
val resolved = outputFile
|
|
162
|
-
|
|
163
|
-
if (!muxerStarted || muxerInstance == null) {
|
|
164
|
-
Log.w(TAG, "stopRecording: muxer never started — discarding empty recording")
|
|
165
|
-
video?.logSummary()
|
|
166
|
-
audio?.logSummary()
|
|
167
|
-
video?.stopAndRelease()
|
|
168
|
-
audio?.stopAndRelease()
|
|
169
|
-
try {
|
|
170
|
-
muxerInstance?.release()
|
|
171
|
-
} catch (_: Throwable) {}
|
|
172
|
-
// Best-effort: delete the empty file so getStreamRecordings()
|
|
173
|
-
// doesn't surface an unplayable 0-byte mp4.
|
|
174
|
-
resolved?.delete()
|
|
175
|
-
fireTerminalCompletion(null, null)
|
|
176
|
-
cleanupAfterStop()
|
|
177
|
-
completion()
|
|
178
|
-
return@post
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Skip the EOS drain when EOS can't be queued — waiting on
|
|
182
|
-
// a marker that will never arrive would hang the handler.
|
|
183
|
-
if (video != null) {
|
|
184
|
-
try {
|
|
185
|
-
val queued = video.signalEndOfStream(muxerInstance)
|
|
186
|
-
if (queued) video.drainAfterEoS(muxerInstance)
|
|
187
|
-
} catch (t: Throwable) {
|
|
188
|
-
Log.e(TAG, "stopRecording: video drain failed", t)
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
if (audio != null) {
|
|
192
|
-
try {
|
|
193
|
-
val queued = audio.signalEndOfStream(muxerInstance)
|
|
194
|
-
if (queued) audio.drainAfterEoS(muxerInstance)
|
|
195
|
-
} catch (t: Throwable) {
|
|
196
|
-
Log.e(TAG, "stopRecording: audio drain failed", t)
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
video?.stopAndRelease()
|
|
201
|
-
audio?.stopAndRelease()
|
|
202
|
-
|
|
203
|
-
var finalResolved: File? = resolved
|
|
204
|
-
try {
|
|
205
|
-
muxerInstance.stop()
|
|
206
|
-
} catch (t: Throwable) {
|
|
207
|
-
Log.e(TAG, "muxer.stop() threw — likely no usable samples", t)
|
|
208
|
-
finalResolved = null
|
|
209
|
-
resolved?.delete()
|
|
210
|
-
}
|
|
211
|
-
try {
|
|
212
|
-
muxerInstance.release()
|
|
213
|
-
} catch (t: Throwable) {
|
|
214
|
-
Log.w(TAG, "muxer.release() threw", t)
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
video?.logSummary()
|
|
218
|
-
audio?.logSummary()
|
|
219
|
-
Log.i(
|
|
220
|
-
TAG,
|
|
221
|
-
"recording finalised → ${finalResolved?.absolutePath ?: "(no file produced)"}",
|
|
222
|
-
)
|
|
223
|
-
|
|
224
|
-
fireTerminalCompletion(finalResolved, null)
|
|
225
|
-
cleanupAfterStop()
|
|
226
|
-
completion()
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
fun clearRecordingsDirectory(context: Context, completion: (Throwable?) -> Unit) {
|
|
231
|
-
handler.post {
|
|
232
|
-
try {
|
|
233
|
-
val dir = recordingsDirectory(context)
|
|
234
|
-
dir.listFiles()?.forEach { it.deleteRecursively() }
|
|
235
|
-
completion(null)
|
|
236
|
-
} catch (t: Throwable) {
|
|
237
|
-
completion(t)
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
fun listRecordings(context: Context): List<File> {
|
|
243
|
-
val dir = File(context.cacheDir, RECORDINGS_DIR_NAME)
|
|
244
|
-
if (!dir.isDirectory) return emptyList()
|
|
245
|
-
return dir.listFiles()?.sortedByDescending { it.lastModified() } ?: emptyList()
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
override fun seedOriginNs(timestampNs: Long): Long {
|
|
249
|
-
val existing = recordingStartHostTimeNs
|
|
250
|
-
if (existing != null) return existing
|
|
251
|
-
recordingStartHostTimeNs = timestampNs
|
|
252
|
-
return timestampNs
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
override fun onTrackAdded() {
|
|
256
|
-
pendingPipelines = (pendingPipelines - 1).coerceAtLeast(0)
|
|
257
|
-
muxer?.let { maybeStartMuxer(it) }
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
override fun onFatalError(error: Throwable) {
|
|
261
|
-
fireTerminalCompletion(null, error)
|
|
262
|
-
cleanupAfterFailure()
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Starts the muxer once every active pipeline has added its track. Calling `start()` before all
|
|
267
|
-
* `addTrack` calls makes subsequent `addTrack` throw "Muxer is not initialized", so the gate is
|
|
268
|
-
* load-bearing.
|
|
269
|
-
*/
|
|
270
|
-
private fun maybeStartMuxer(muxerInstance: MediaMuxer) {
|
|
271
|
-
if (muxerStarted) return
|
|
272
|
-
if (pendingPipelines > 0) return
|
|
273
|
-
|
|
274
|
-
try {
|
|
275
|
-
muxerInstance.start()
|
|
276
|
-
muxerStarted = true
|
|
277
|
-
} catch (t: Throwable) {
|
|
278
|
-
Log.e(TAG, "muxer.start() threw", t)
|
|
279
|
-
fireTerminalCompletion(null, t)
|
|
280
|
-
cleanupAfterFailure()
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
private fun fireTerminalCompletion(file: File?, error: Throwable?) {
|
|
285
|
-
if (isCompleted) return
|
|
286
|
-
isCompleted = true
|
|
287
|
-
|
|
288
|
-
val cb = recordingCompletion
|
|
289
|
-
recordingCompletion = null
|
|
290
|
-
cb?.invoke(file, error)
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Resets every transient field to its initial value. Single source of truth for "the manager is
|
|
295
|
-
* between recordings". Does NOT release native resources — the caller must stop/release
|
|
296
|
-
* encoders and the muxer before invoking this.
|
|
297
|
-
*/
|
|
298
|
-
private fun resetTransientState() {
|
|
299
|
-
muxer = null
|
|
300
|
-
muxerStarted = false
|
|
301
|
-
videoPipeline = null
|
|
302
|
-
audioPipeline = null
|
|
303
|
-
outputFile = null
|
|
304
|
-
recordingCompletion = null
|
|
305
|
-
isCompleted = false
|
|
306
|
-
isRecording = false
|
|
307
|
-
pendingPipelines = 0
|
|
308
|
-
recordingStartHostTimeNs = null
|
|
309
|
-
autoStopRunnable?.let { timerHandler.removeCallbacks(it) }
|
|
310
|
-
autoStopRunnable = null
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
private fun cleanupAfterFailure() {
|
|
314
|
-
videoPipeline?.detachSink()
|
|
315
|
-
audioPipeline?.detachSink()
|
|
316
|
-
videoPipeline?.stopAndRelease()
|
|
317
|
-
audioPipeline?.stopAndRelease()
|
|
318
|
-
try {
|
|
319
|
-
muxer?.release()
|
|
320
|
-
} catch (t: Throwable) {
|
|
321
|
-
Log.w(TAG, "failed to release muxer", t)
|
|
322
|
-
}
|
|
323
|
-
resetTransientState()
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
private fun cleanupAfterStop() {
|
|
327
|
-
resetTransientState()
|
|
328
|
-
}
|
|
329
|
-
}
|