fix(android): include mpv end-file diagnostics
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
package com.edde746.plezy.mpv
|
||||
|
||||
import dev.jdtech.mpv.EndFileReason
|
||||
import dev.jdtech.mpv.LogLevel
|
||||
import dev.jdtech.mpv.LogMessage
|
||||
import dev.jdtech.mpv.MpvEvent
|
||||
|
||||
/** Adds the native diagnostic that libmpv-android exposes separately via logFlow. */
|
||||
internal class MpvEndFileDiagnostics {
|
||||
private var errorMessage: String? = null
|
||||
|
||||
fun onStartFile() {
|
||||
errorMessage = null
|
||||
}
|
||||
|
||||
fun onLogMessage(message: LogMessage) {
|
||||
if (message.level == LogLevel.Fatal || message.level == LogLevel.Error) {
|
||||
errorMessage = message.text.takeIf { it.isNotBlank() }
|
||||
}
|
||||
}
|
||||
|
||||
fun onEndFile(event: MpvEvent.EndFile): Map<String, Any>? {
|
||||
val reason = event.reason
|
||||
if (reason == null) {
|
||||
errorMessage = null
|
||||
return null
|
||||
}
|
||||
val data = mutableMapOf<String, Any>("reason" to reason.id)
|
||||
if (reason == EndFileReason.Error) {
|
||||
errorMessage?.let { data["message"] = it }
|
||||
}
|
||||
errorMessage = null
|
||||
return data
|
||||
}
|
||||
}
|
||||
@@ -73,6 +73,7 @@ class MpvPlayerCore(
|
||||
|
||||
@Volatile private var player: MpvPlayer? = null
|
||||
private var scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
||||
private val endFileDiagnostics = MpvEndFileDiagnostics()
|
||||
|
||||
// mpv writes must stay off the main thread but run in submission order:
|
||||
// setupMpvFallback sets vo/ao/hwdec immediately before the loadfile command,
|
||||
@@ -184,6 +185,7 @@ class MpvPlayerCore(
|
||||
|
||||
try {
|
||||
disposing = false
|
||||
endFileDiagnostics.onStartFile()
|
||||
cachedPaused = true
|
||||
pausedForSurfaceLoss = false
|
||||
pendingSurface = null
|
||||
@@ -361,9 +363,9 @@ class MpvPlayerCore(
|
||||
p.eventFlow.collect { event ->
|
||||
when (event) {
|
||||
is MpvEvent.EndFile -> {
|
||||
val data = event.reason?.let { mapOf("reason" to it.id) }
|
||||
delegate?.onEvent("end-file", data)
|
||||
delegate?.onEvent("end-file", endFileDiagnostics.onEndFile(event))
|
||||
}
|
||||
is MpvEvent.StartFile -> endFileDiagnostics.onStartFile()
|
||||
is MpvEvent.FileLoaded -> delegate?.onEvent("file-loaded", null)
|
||||
is MpvEvent.PlaybackRestart -> delegate?.onEvent("playback-restart", null)
|
||||
else -> {}
|
||||
@@ -397,6 +399,7 @@ class MpvPlayerCore(
|
||||
private fun collectLogMessages(p: MpvPlayer) {
|
||||
scope.launch(start = CoroutineStart.UNDISPATCHED) {
|
||||
p.logFlow.collect { msg ->
|
||||
endFileDiagnostics.onLogMessage(msg)
|
||||
emitLog(msg.level.name.lowercase(), msg.prefix, msg.text)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
package com.edde746.plezy.mpv
|
||||
|
||||
import dev.jdtech.mpv.EndFileReason
|
||||
import dev.jdtech.mpv.LogLevel
|
||||
import dev.jdtech.mpv.LogMessage
|
||||
import dev.jdtech.mpv.MpvEvent
|
||||
import io.flutter.plugin.common.EventChannel
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import org.junit.Assert.assertEquals
|
||||
@@ -37,6 +42,59 @@ class MpvPlayerPluginTest {
|
||||
assertNull(result.successValue)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun endFileDiagnosticsPreserveReasonIdAndExposeDependencyErrorLog() {
|
||||
val diagnostics = MpvEndFileDiagnostics()
|
||||
diagnostics.onStartFile()
|
||||
diagnostics.onLogMessage(LogMessage("ffmpeg", LogLevel.Error, "Invalid data found when processing input"))
|
||||
|
||||
assertEquals(
|
||||
mapOf(
|
||||
"reason" to 4,
|
||||
"message" to "Invalid data found when processing input"
|
||||
),
|
||||
diagnostics.onEndFile(MpvEvent.EndFile(EndFileReason.Error))
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun endFileDiagnosticsDoNotAttachStaleOrInventedDetails() {
|
||||
val diagnostics = MpvEndFileDiagnostics()
|
||||
diagnostics.onLogMessage(LogMessage("ffmpeg", LogLevel.Error, "old failure"))
|
||||
diagnostics.onStartFile()
|
||||
|
||||
assertEquals(mapOf("reason" to 0), diagnostics.onEndFile(MpvEvent.EndFile(EndFileReason.Eof)))
|
||||
assertEquals(mapOf("reason" to 4), diagnostics.onEndFile(MpvEvent.EndFile(EndFileReason.Error)))
|
||||
assertNull(diagnostics.onEndFile(MpvEvent.EndFile(null)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun endFileEventChannelPayloadKeepsExistingEnvelopeAndAddsMessage() {
|
||||
val sink = RecordingEventSink()
|
||||
val plugin = MpvPlayerPlugin()
|
||||
plugin.onListen(null, sink)
|
||||
|
||||
plugin.onEvent(
|
||||
"end-file",
|
||||
mapOf(
|
||||
"reason" to 4,
|
||||
"message" to "Failed to open stream"
|
||||
)
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
mapOf(
|
||||
"type" to "event",
|
||||
"name" to "end-file",
|
||||
"data" to mapOf(
|
||||
"reason" to 4,
|
||||
"message" to "Failed to open stream"
|
||||
)
|
||||
),
|
||||
sink.successValue
|
||||
)
|
||||
}
|
||||
|
||||
private class RecordingResult : MethodChannel.Result {
|
||||
var successValue: Any? = null
|
||||
var errorCode: String? = null
|
||||
@@ -51,4 +109,16 @@ class MpvPlayerPluginTest {
|
||||
|
||||
override fun notImplemented() = Unit
|
||||
}
|
||||
|
||||
private class RecordingEventSink : EventChannel.EventSink {
|
||||
var successValue: Any? = null
|
||||
|
||||
override fun success(event: Any?) {
|
||||
successValue = event
|
||||
}
|
||||
|
||||
override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) = Unit
|
||||
|
||||
override fun endOfStream() = Unit
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:plezy/mpv/models.dart';
|
||||
import 'package:plezy/mpv/player/player_native.dart';
|
||||
import 'package:plezy/services/settings_service.dart';
|
||||
|
||||
@@ -62,4 +63,52 @@ void main() {
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('Android mpv end-file error preserves native diagnostic message', () async {
|
||||
await withMockPlayerChannels(
|
||||
methodChannelName: 'com.plezy/mpv_player',
|
||||
eventChannelName: 'com.plezy/mpv_player/events',
|
||||
testBody: () async {
|
||||
final player = PlayerNative();
|
||||
final error = player.streams.error.first;
|
||||
try {
|
||||
player.handlePlayerEvent('end-file', {'reason': 4, 'message': 'Invalid data found when processing input'});
|
||||
|
||||
await expectLater(
|
||||
error,
|
||||
completion(
|
||||
isA<PlayerError>().having(
|
||||
(value) => value.message,
|
||||
'message',
|
||||
'Invalid data found when processing input',
|
||||
),
|
||||
),
|
||||
);
|
||||
} finally {
|
||||
await player.dispose();
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('Android mpv legacy end-file error keeps playback error fallback', () async {
|
||||
await withMockPlayerChannels(
|
||||
methodChannelName: 'com.plezy/mpv_player',
|
||||
eventChannelName: 'com.plezy/mpv_player/events',
|
||||
testBody: () async {
|
||||
final player = PlayerNative();
|
||||
final error = player.streams.error.first;
|
||||
try {
|
||||
player.handlePlayerEvent('end-file', {'reason': 4});
|
||||
|
||||
await expectLater(
|
||||
error,
|
||||
completion(isA<PlayerError>().having((value) => value.message, 'message', 'Playback error')),
|
||||
);
|
||||
} finally {
|
||||
await player.dispose();
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user