playScene method

Future<void> playScene(
  1. Scene scene,
  2. String streamUrl, {
  3. String? mimeType,
  4. String? streamLabel,
  5. String? streamSource,
  6. Map<String, String>? httpHeaders,
  7. bool? prewarmAttempted,
  8. bool? prewarmSucceeded,
  9. int? prewarmLatencyMs,
})

Implementation

Future<void> playScene(
  Scene scene,
  String streamUrl, {
  String? mimeType,
  String? streamLabel,
  String? streamSource,
  Map<String, String>? httpHeaders,
  bool? prewarmAttempted,
  bool? prewarmSucceeded,
  int? prewarmLatencyMs,
}) async {
  final allowBackgroundPlayback = state.enableBackgroundPlayback;

  AppLogStore.instance.add(
    'provider playScene begin scene=${scene.id} source=${streamSource ?? '-'} mime=${mimeType ?? '-'}',
    source: 'player_provider',
  );

  if (state.activeScene?.id == scene.id &&
      state.videoPlayerController != null) {
    _videoControllerRef ??= state.videoPlayerController;
    state.videoPlayerController?.play();
    AppLogStore.instance.add(
      'provider playScene replay-active scene=${scene.id}',
      source: 'player_provider',
    );
    state = state.copyWith(
      isPlaying: true,
      streamMimeType: mimeType,
      streamLabel: streamLabel,
      streamSource: streamSource,
      prewarmAttempted: prewarmAttempted,
      prewarmSucceeded: prewarmSucceeded,
      prewarmLatencyMs: prewarmLatencyMs,
    );
    return;
  }

  // Detach the currently rendered player widget before disposing native players.
  if (state.activeScene != null) {
    state = state.copyWith(clearActive: true, isPlaying: false);
  }

  // Stop current
  await _disposeControllers();
  if (!ref.mounted) return;

  final stopwatch = Stopwatch()..start();
  final videoController = VideoPlayerController.networkUrl(
    Uri.parse(streamUrl),
    httpHeaders: httpHeaders ?? const <String, String>{},
    videoPlayerOptions: VideoPlayerOptions(
      allowBackgroundPlayback: allowBackgroundPlayback,
    ),
  );
  _videoControllerRef = videoController;
  _firstFrameLoggedSceneId = null;

  state = state.copyWith(
    activeScene: scene,
    videoPlayerController: videoController,
    isPlaying: false,
    streamMimeType: mimeType,
    streamLabel: streamLabel,
    streamSource: streamSource,
    startupLatencyMs: null,
    prewarmAttempted: prewarmAttempted,
    prewarmSucceeded: prewarmSucceeded,
    prewarmLatencyMs: prewarmLatencyMs,
  );

  try {
    await videoController.initialize();
    if (!ref.mounted) {
      await _disposeControllers();
      return;
    }
    stopwatch.stop();
    final initializeElapsedMs = stopwatch.elapsedMilliseconds;
    AppLogStore.instance.add(
      'provider initialize done scene=${scene.id} elapsed=${initializeElapsedMs}ms duration=${videoController.value.duration.inMilliseconds}ms size=${videoController.value.size.width.toStringAsFixed(0)}x${videoController.value.size.height.toStringAsFixed(0)}',
      source: 'player_provider',
    );

    state = state.copyWith(
      isPlaying: true,
      startupLatencyMs: initializeElapsedMs,
    );

    mediaHandler?.updateMetadata(
      id: scene.id,
      title: scene.title,
      studio: scene.studioName,
      thumbnailUri: scene.paths.screenshot,
      duration: videoController.value.duration,
    );

    AppLogStore.instance.add(
      'provider ready scene=${scene.id} startup=${initializeElapsedMs}ms',
      source: 'player_provider',
    );

    unawaited(WakelockPlus.enable());

    videoController.addListener(_videoListener);
    unawaited(videoController.play());
  } catch (e) {
    debugPrint('Error initializing video player: $e');
    AppLogStore.instance.add(
      'provider initialize error scene=${scene.id} error=$e',
      source: 'player_provider',
    );
    if (ref.mounted) {
      stop();
    } else {
      await _disposeControllers();
    }
  }
}