A coalesced key-repeat skip pins its target so a slow backend cannot make the next press rebase off a position the seek has not reached yet. Nothing retired that pin when something else moved the playhead, so for the ten seconds it survived, a skip taken after a timeline tap, a chapter jump, an OS media control or a peer sync resumed from the superseded target and threw the user back across their own jump. Publish every playhead movement on the player and retire the pin whenever the announced destination is not the accumulator's own commit. Overlapping seeks and backend-chosen relocations arbitrate by which operation the backend accepted, so a request that was merely asked for cannot speak for where the playhead ended up. close #1819
88 lines
3.9 KiB
Dart
88 lines
3.9 KiB
Dart
import 'dart:async';
|
|
|
|
import '../models.dart';
|
|
import 'player_streams.dart';
|
|
|
|
mixin PlayerStreamControllersMixin {
|
|
final playingController = StreamController<bool>.broadcast();
|
|
final completedController = StreamController<bool>.broadcast();
|
|
final bufferingController = StreamController<bool>.broadcast();
|
|
final positionController = StreamController<Duration>.broadcast();
|
|
final playheadJumpController = StreamController<Duration?>.broadcast();
|
|
final durationController = StreamController<Duration>.broadcast();
|
|
final seekableController = StreamController<bool>.broadcast();
|
|
final bufferController = StreamController<Duration>.broadcast();
|
|
final volumeController = StreamController<double>.broadcast();
|
|
final rateController = StreamController<double>.broadcast();
|
|
final tracksController = StreamController<Tracks>.broadcast();
|
|
final trackController = StreamController<TrackSelection>.broadcast();
|
|
final logController = StreamController<PlayerLog>.broadcast();
|
|
final errorController = StreamController<PlayerError>.broadcast();
|
|
final audioDeviceController = StreamController<AudioDevice>.broadcast();
|
|
final audioDevicesController = StreamController<List<AudioDevice>>.broadcast();
|
|
final bufferRangesController = StreamController<List<BufferRange>>.broadcast();
|
|
final playbackRestartController = StreamController<void>.broadcast();
|
|
final fileLoadedController = StreamController<void>.broadcast();
|
|
final fileStartedController = StreamController<void>.broadcast();
|
|
final fileLoadFailedController = StreamController<void>.broadcast();
|
|
final primaryMediaReadyController = StreamController<void>.broadcast();
|
|
final backendSwitchedController = StreamController<void>.broadcast();
|
|
final trackTransitionController = StreamController<String>.broadcast();
|
|
|
|
PlayerStreams createStreams() {
|
|
return PlayerStreams(
|
|
playing: playingController.stream,
|
|
completed: completedController.stream,
|
|
buffering: bufferingController.stream,
|
|
position: positionController.stream,
|
|
playheadJump: playheadJumpController.stream,
|
|
duration: durationController.stream,
|
|
seekable: seekableController.stream,
|
|
buffer: bufferController.stream,
|
|
volume: volumeController.stream,
|
|
rate: rateController.stream,
|
|
tracks: tracksController.stream,
|
|
track: trackController.stream,
|
|
log: logController.stream,
|
|
error: errorController.stream,
|
|
audioDevice: audioDeviceController.stream,
|
|
audioDevices: audioDevicesController.stream,
|
|
bufferRanges: bufferRangesController.stream,
|
|
playbackRestart: playbackRestartController.stream,
|
|
fileLoaded: fileLoadedController.stream,
|
|
fileStarted: fileStartedController.stream,
|
|
fileLoadFailed: fileLoadFailedController.stream,
|
|
primaryMediaReady: primaryMediaReadyController.stream,
|
|
backendSwitched: backendSwitchedController.stream,
|
|
trackTransition: trackTransitionController.stream,
|
|
);
|
|
}
|
|
|
|
Future<void> closeStreamControllers() async {
|
|
await playingController.close();
|
|
await completedController.close();
|
|
await bufferingController.close();
|
|
await positionController.close();
|
|
await playheadJumpController.close();
|
|
await durationController.close();
|
|
await seekableController.close();
|
|
await bufferController.close();
|
|
await volumeController.close();
|
|
await rateController.close();
|
|
await tracksController.close();
|
|
await trackController.close();
|
|
await logController.close();
|
|
await errorController.close();
|
|
await audioDeviceController.close();
|
|
await audioDevicesController.close();
|
|
await bufferRangesController.close();
|
|
await playbackRestartController.close();
|
|
await fileLoadedController.close();
|
|
await fileStartedController.close();
|
|
await fileLoadFailedController.close();
|
|
await primaryMediaReadyController.close();
|
|
await backendSwitchedController.close();
|
|
await trackTransitionController.close();
|
|
}
|
|
}
|