fix(exoplayer): correct transcode seek position double-count
This commit is contained in:
@@ -103,9 +103,12 @@ class PlayerAndroid extends PlayerBase {
|
|||||||
if (disposed) return;
|
if (disposed) return;
|
||||||
await _ensureInitialized();
|
await _ensureInitialized();
|
||||||
final startPosition = media.start ?? Duration.zero;
|
final startPosition = media.start ?? Duration.zero;
|
||||||
configureTimeline(offset: timelineOffset, duration: timelineDuration);
|
// ExoPlayer reports Plex copyts transcodes in source-time coordinates,
|
||||||
|
// unlike mpv which rebases them to zero. Do not add the timeline offset
|
||||||
|
// again on Android ExoPlayer or seeks/progress jump to roughly 2x (#1221).
|
||||||
|
configureTimeline(offset: Duration.zero, duration: timelineDuration);
|
||||||
clearTracks();
|
clearTracks();
|
||||||
resetPlaybackProgress(startPosition);
|
resetPlaybackProgress(media.start ?? timelineOffset);
|
||||||
setSeekable(false);
|
setSeekable(false);
|
||||||
|
|
||||||
// Show the video layer
|
// Show the video layer
|
||||||
|
|||||||
@@ -133,6 +133,56 @@ void main() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('ExoPlayer maps copyts transcode streams as absolute timeline positions', () async {
|
||||||
|
final calls = <MethodCall>[];
|
||||||
|
|
||||||
|
await _withMockChannels(
|
||||||
|
methodChannelName: 'com.plezy/exo_player',
|
||||||
|
eventChannelName: 'com.plezy/exo_player/events',
|
||||||
|
methodHandler: (call) {
|
||||||
|
calls.add(call);
|
||||||
|
switch (call.method) {
|
||||||
|
case 'initialize':
|
||||||
|
return Future.value(true);
|
||||||
|
default:
|
||||||
|
return Future.value(null);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
testBody: () async {
|
||||||
|
final player = PlayerAndroid();
|
||||||
|
try {
|
||||||
|
const timelineStart = Duration(seconds: 2058); // 34:18
|
||||||
|
const timelineDuration = Duration(seconds: 2903); // 48:23
|
||||||
|
await player.open(
|
||||||
|
Media('https://example.test/transcode.mkv'),
|
||||||
|
timelineOffset: timelineStart,
|
||||||
|
timelineDuration: timelineDuration,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(player.state.position, timelineStart);
|
||||||
|
expect(player.state.duration, timelineDuration);
|
||||||
|
|
||||||
|
final openCall = calls.singleWhere((call) => call.method == 'open');
|
||||||
|
final openArgs = Map<Object?, Object?>.from(openCall.arguments as Map);
|
||||||
|
expect(openArgs['startPositionMs'], 0);
|
||||||
|
|
||||||
|
await Future<void>.delayed(const Duration(milliseconds: 260));
|
||||||
|
player.handlePropertyChange('time-pos', 2058.0);
|
||||||
|
expect(player.state.position, timelineStart);
|
||||||
|
|
||||||
|
await player.seek(const Duration(minutes: 40));
|
||||||
|
|
||||||
|
final seekCall = calls.lastWhere((call) => call.method == 'seek');
|
||||||
|
final seekArgs = Map<Object?, Object?>.from(seekCall.arguments as Map);
|
||||||
|
expect(seekArgs['positionMs'], const Duration(minutes: 40).inMilliseconds);
|
||||||
|
expect(player.state.position, const Duration(minutes: 40));
|
||||||
|
} finally {
|
||||||
|
await player.dispose();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('MPV clears stale Dart track state before opening new media', () async {
|
test('MPV clears stale Dart track state before opening new media', () async {
|
||||||
await _withMockChannels(
|
await _withMockChannels(
|
||||||
methodChannelName: 'com.plezy/mpv_player',
|
methodChannelName: 'com.plezy/mpv_player',
|
||||||
|
|||||||
Reference in New Issue
Block a user