import 'dart:async'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:media_kit/media_kit.dart'; import 'package:media_kit_video/media_kit_video.dart'; import 'package:mirror_protocol/mirror_protocol.dart'; import 'display_state_provider.dart'; final playerProvider = Provider((ref) { final service = PlayerService(ref); ref.onDispose(() => service.dispose()); return service; }); final videoControllerProvider = Provider((ref) { return ref.watch(playerProvider).videoController; }); class PlayerService { final Ref _ref; late final Player _player; late final VideoController _videoController; StreamSubscription? _positionSub; StreamSubscription? _playingSub; StreamSubscription? _completedSub; StreamSubscription? _durationSub; Timer? _progressSaveTimer; PlayerService(this._ref) { _player = Player(); _videoController = VideoController(_player); _positionSub = _player.stream.position.listen((position) { _ref.read(displayStateProvider.notifier).updatePlaybackPosition(position); }); _playingSub = _player.stream.playing.listen((playing) { final state = _ref.read(displayStateProvider); final current = state.playback; if (current != null && current.isPlaying != playing) { _ref.read(displayStateProvider.notifier).updatePlaybackPosition( _player.state.position, ); } }); _durationSub = _player.stream.duration.listen((duration) { final state = _ref.read(displayStateProvider); final current = state.playback; if (current != null && current.duration != duration) { // Update duration if it wasn't known initially } }); _completedSub = _player.stream.completed.listen((completed) { if (completed) { _ref.read(displayStateProvider.notifier).onMediaCompleted(); } }); } VideoController get videoController => _videoController; Player get player => _player; Future play(String url, {Duration? startPosition}) async { await _player.open(Media(url)); if (startPosition != null && startPosition > Duration.zero) { // Wait briefly for the player to initialize before seeking await Future.delayed(const Duration(milliseconds: 500)); await _player.seek(startPosition); } _startProgressSaving(); } Future pause() async => await _player.pause(); Future resume() async => await _player.play(); Future seek(Duration position) async => await _player.seek(position); Future setVolume(double volume) async => await _player.setVolume(volume * 100); Future stop() async { _stopProgressSaving(); await _player.stop(); } void _startProgressSaving() { _stopProgressSaving(); _progressSaveTimer = Timer.periodic(const Duration(seconds: 10), (_) { final state = _ref.read(displayStateProvider); final workout = state.workout; final playback = state.playback; if (workout != null && workout.phase == WorkoutPhase.playing && playback != null && playback.source == MediaSource.workout) { final client = _ref.read(workoutApiClientProvider); final percent = playback.duration.inSeconds > 0 ? (playback.position.inSeconds / playback.duration.inSeconds * 100).round() : 0; client.updateProgress( videoId: playback.mediaId, position: playback.position.inSeconds, percent: percent, ); } }); } void _stopProgressSaving() { _progressSaveTimer?.cancel(); _progressSaveTimer = null; } void dispose() { _stopProgressSaving(); _positionSub?.cancel(); _playingSub?.cancel(); _completedSub?.cancel(); _durationSub?.cancel(); _player.dispose(); } }