Skip to content

Commit d1bf570

Browse files
committed
feat: implement startPosition support in player loading
1 parent 7724b98 commit d1bf570

9 files changed

Lines changed: 73 additions & 36 deletions

File tree

lib/models/playback/playback_model.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,8 @@ class PlaybackModelHelper {
246246

247247
final firstItemIsSynced = syncedItem != null && syncedItem.status == TaskStatus.complete;
248248

249+
final actualStartPosition = startPosition ?? fullItem.userData.playBackPosition;
250+
249251
final options = {
250252
PlaybackType.directStream,
251253
PlaybackType.transcode,
@@ -269,7 +271,7 @@ class PlaybackModelHelper {
269271
forcedPlaybackType ?? playbackType,
270272
oldModel: oldModel,
271273
libraryQueue: queue,
272-
startPosition: startPosition,
274+
startPosition: actualStartPosition,
273275
),
274276
PlaybackType.offline => await _createOfflinePlaybackModel(
275277
fullItem,
@@ -283,7 +285,7 @@ class PlaybackModelHelper {
283285
fullItem,
284286
item.streamModel,
285287
forcedPlaybackType ?? PlaybackType.directStream,
286-
startPosition: startPosition,
288+
startPosition: actualStartPosition,
287289
oldModel: oldModel,
288290
libraryQueue: queue,
289291
)) ??

lib/providers/video_player_provider.dart

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -126,24 +126,12 @@ class VideoPlayerNotifier extends StateNotifier<MediaControlsWrapper> {
126126
PlaybackModel? newPlaybackModel = model;
127127

128128
if (media != null) {
129-
await state.loadVideo(model, startPosition, false);
129+
await state.loadVideo(model, startPosition, true);
130130
await state.setVolume(ref.read(videoPlayerSettingsProvider).volume);
131131

132-
state.stateStream?.takeWhile((event) => event.buffering == true).listen(
133-
null,
134-
onDone: () async {
135-
final start = startPosition;
136-
if (start != Duration.zero) {
137-
await state.seek(start);
138-
}
139-
await state.setAudioTrack(null, model);
140-
await state.setSubtitleTrack(null, model);
141-
state.play();
142-
ref.read(playBackModel.notifier).update((state) => newPlaybackModel);
143-
},
144-
);
145-
146-
ref.read(playBackModel.notifier).update((state) => model);
132+
await state.setAudioTrack(null, model);
133+
await state.setSubtitleTrack(null, model);
134+
ref.read(playBackModel.notifier).update((state) => newPlaybackModel);
147135

148136
return true;
149137
}

lib/stubs/web/lib_mdk_web.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class LibMDK extends BasePlayer {
2424
Future<void> dispose() async {}
2525

2626
@override
27-
Future<void> loadVideo(String url, bool play) async {}
27+
Future<void> loadVideo(String url, bool play, {Duration startPosition = Duration.zero}) async {}
2828

2929
void setState(PlayerState state) {}
3030

lib/util/item_base_model/play_item_helpers.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,9 @@ extension ItemBaseModelExtensions on ItemBaseModel? {
222222
return;
223223
}
224224

225-
await _playVideo(context, startPosition: startPosition, current: model, ref: ref, cancelOperation: op);
225+
final actualStartPosition = startPosition ?? await model.startDuration() ?? Duration.zero;
226+
227+
await _playVideo(context, startPosition: actualStartPosition, current: model, ref: ref, cancelOperation: op);
226228
}
227229
}
228230

lib/wrappers/media_control_wrapper.dart

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,10 @@ class MediaControlsWrapper extends BaseAudioHandler implements VideoPlayerContro
118118
final context = ref.read(localizationContextProvider);
119119
await (_player as NativePlayer).sendPlaybackDataToNative(context, model, startPosition);
120120
}
121-
await _player?.loadVideo(model.media?.url ?? "", play);
121+
await _player?.loadVideo(model.media?.url ?? "", play, startPosition: startPosition);
122+
if (play) {
123+
ref.read(playBackModel)?.playbackStarted(startPosition, ref);
124+
}
122125
_player?.applySubtitleSettings(ref.read(subtitleSettingsProvider));
123126
}
124127

@@ -223,8 +226,12 @@ class MediaControlsWrapper extends BaseAudioHandler implements VideoPlayerContro
223226
Future<void> play() async {
224227
WakelockPlus.enable();
225228
_player?.play();
229+
226230
final currentPosition = await ref.read(playBackModel.select((value) => value?.startDuration()));
227-
ref.read(playBackModel)?.playbackStarted(currentPosition ?? Duration.zero, ref);
231+
final isPlaying = playbackState.value.playing;
232+
if (!isPlaying) {
233+
ref.read(playBackModel)?.playbackStarted(currentPosition ?? Duration.zero, ref);
234+
}
228235

229236
final playBackItem = ref.read(playBackModel.select((value) => value?.item));
230237
if (playBackItem == null) return;

lib/wrappers/players/base_player.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ abstract class BasePlayer {
2626
});
2727
Future<void> dispose();
2828
Future<void> open(BuildContext context);
29-
Future<void> loadVideo(String url, bool play);
29+
Future<void> loadVideo(String url, bool play, {Duration startPosition = Duration.zero});
3030
Future<void> seek(Duration position);
3131
Future<void> play();
3232
Future<void> setVolume(double volume);

lib/wrappers/players/lib_mdk.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ class LibMDK extends BasePlayer {
6868
}
6969

7070
@override
71-
Future<void> loadVideo(String url, bool play) async {
71+
Future<void> loadVideo(String url, bool play, {Duration startPosition = Duration.zero}) async {
7272
_controller?.dispose();
7373

7474
final validUrl = isValidUrl(url);
@@ -81,6 +81,10 @@ class LibMDK extends BasePlayer {
8181
await _controller?.initialize();
8282
_controller?.addListener(() => updateState());
8383

84+
if (startPosition != Duration.zero) {
85+
await _controller?.seekTo(startPosition);
86+
}
87+
8488
if (play) {
8589
await _controller?.play();
8690
}

lib/wrappers/players/lib_mpv.dart

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,19 @@ class LibMPV extends BasePlayer {
9494
}
9595

9696
@override
97-
Future<void> loadVideo(String url, bool play) async {
97+
Future<void> loadVideo(String url, bool play, {Duration startPosition = Duration.zero}) async {
9898
_loadCompleter = Completer<void>();
99-
await _player?.open(mpv.Media(url), play: play);
10099
_firstLoadAttempt = DateTime.now();
101100

101+
if (_player?.platform is mpv.NativePlayer) {
102+
await (_player?.platform as dynamic).setProperty(
103+
'start',
104+
'${startPosition.inMilliseconds / 1000}',
105+
);
106+
}
107+
108+
await _player?.open(mpv.Media(url), play: play);
109+
102110
_retryTimer?.cancel();
103111
_retryTimer = null;
104112

@@ -111,21 +119,46 @@ class LibMPV extends BasePlayer {
111119
_retryTimer?.cancel();
112120
_retryTimer = null;
113121
} else {
114-
if (lastState.buffering == false) {
115-
_finishedLoading();
116-
} else {
117-
log("Retrying to load video $url");
118-
_player?.open(mpv.Media(url), play: play);
119-
_retryTimer?.reset();
122+
log("Retrying to load video $url");
123+
if (_player?.platform is mpv.NativePlayer) {
124+
await (_player?.platform as dynamic).setProperty(
125+
'start',
126+
'${startPosition.inMilliseconds / 1000}',
127+
);
120128
}
129+
await _player?.open(mpv.Media(url), play: play);
130+
_retryTimer?.reset();
121131
}
122132
},
123133
);
134+
135+
// Wait for the player to be ready
136+
if (_loadCompleter?.isCompleted == false) {
137+
StreamSubscription? subBuffering;
138+
StreamSubscription? subDuration;
139+
140+
void onReady() {
141+
if (_loadCompleter?.isCompleted == true) return;
142+
_finishedLoading();
143+
subBuffering?.cancel();
144+
subDuration?.cancel();
145+
}
146+
147+
subBuffering = _player?.stream.buffering.listen((event) {
148+
if (event == false && (_player?.state.duration ?? Duration.zero) > Duration.zero) {
149+
onReady();
150+
}
151+
});
152+
subDuration = _player?.stream.duration.listen((event) {
153+
if (event > Duration.zero) onReady();
154+
});
155+
}
156+
124157
_loadCompleter?.future.then(
125158
(value) async {
126-
await Future.delayed(const Duration(milliseconds: 150));
127-
if (play && !lastState.playing) {
128-
await _player?.play();
159+
// Backup seek in case property didn't work
160+
if (startPosition != Duration.zero && (_player?.state.position.inSeconds ?? 0) < startPosition.inSeconds - 5) {
161+
await _player?.seek(startPosition);
129162
}
130163
},
131164
);

lib/wrappers/players/native_player.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ class NativePlayer extends BasePlayer implements VideoPlayerListenerCallback {
3535
}
3636

3737
@override
38-
Future<void> loadVideo(String url, bool play) async => player.open(url, play);
38+
Future<void> loadVideo(String url, bool play, {Duration startPosition = Duration.zero}) async =>
39+
player.open(url, play);
3940

4041
@override
4142
Future<StartResult> open(BuildContext newContext) async {

0 commit comments

Comments
 (0)