import 'package:flutter/material.dart'; // ignore_for_file: public_member_api_docs, sort_constructors_first import 'package:collection/collection.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:iconsax_plus/iconsax_plus.dart'; import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart'; import 'package:fladder/models/items/chapters_model.dart'; import 'package:fladder/models/items/item_stream_model.dart'; import 'package:fladder/models/items/media_segments_model.dart'; import 'package:fladder/models/items/media_streams_model.dart'; import 'package:fladder/models/syncing/sync_item.dart'; import 'package:fladder/providers/user_provider.dart'; import 'package:fladder/util/localization_helper.dart'; enum PlaybackType { directStream, offline, transcode; IconData get icon => switch (this) { PlaybackType.offline => IconsaxPlusLinear.cloud, PlaybackType.directStream => IconsaxPlusLinear.arrow_right_1, PlaybackType.transcode => IconsaxPlusLinear.convert, }; String name(BuildContext context) => switch (this) { PlaybackType.directStream => context.localized.playbackTypeDirect, PlaybackType.offline => context.localized.playbackTypeOffline, PlaybackType.transcode => context.localized.playbackTypeTranscode }; } class VideoPlayback { final List queue; final ItemStreamModel? currentItem; final SyncedItem? currentSyncedItem; final VideoStream? currentStream; VideoPlayback({ required this.queue, this.currentItem, this.currentSyncedItem, this.currentStream, }); List get subStreams => currentStream?.mediaStreamsModel?.subStreams ?? []; List get audioStreams => currentStream?.mediaStreamsModel?.audioStreams ?? []; ItemStreamModel? get previousVideo { final int currentIndex = queue.indexWhere((element) => element.id == currentItem?.id); if (currentIndex > 0) { return queue[currentIndex - 1]; } return null; } ItemStreamModel? get nextVideo { final int currentIndex = queue.indexWhere((element) => element.id == currentItem?.id); if ((currentIndex + 1) < queue.length) { return queue[currentIndex + 1]; } return null; } VideoPlayback copyWith({ List? queue, ItemStreamModel? currentItem, SyncedItem? currentSyncedItem, VideoStream? currentStream, }) { return VideoPlayback( queue: queue ?? this.queue, currentItem: currentItem ?? this.currentItem, currentSyncedItem: currentSyncedItem ?? this.currentSyncedItem, currentStream: currentStream ?? this.currentStream, ); } VideoPlayback clear() { return VideoPlayback( queue: queue, currentItem: null, currentStream: null, ); } } class VideoStream { final String id; final Duration? currentPosition; final PlaybackType playbackType; final String playbackUrl; final String playSessionId; final List? chapters; final List? trickPlay; final MediaSegmentsModel? mediaSegments; final int? audioStreamIndex; final int? subtitleStreamIndex; final MediaStreamsModel? mediaStreamsModel; AudioStreamModel? get currentAudioStream { if (audioStreamIndex == -1 || audioStreamIndex == null) { return null; } return mediaStreamsModel?.audioStreams.firstWhereOrNull( (element) => element.index == (audioStreamIndex ?? mediaStreamsModel?.currentAudioStream ?? 0)); } SubStreamModel? get currentSubStream { if (subtitleStreamIndex == -1 || subtitleStreamIndex == null) { return null; } return mediaStreamsModel?.subStreams.firstWhereOrNull( (element) => element.index == (subtitleStreamIndex ?? mediaStreamsModel?.currentSubStream ?? 0)); } VideoStream({ required this.id, this.currentPosition, required this.playbackType, required this.playbackUrl, required this.playSessionId, this.chapters, this.trickPlay, this.mediaSegments, this.audioStreamIndex, this.subtitleStreamIndex, this.mediaStreamsModel, }); VideoStream copyWith({ String? id, Duration? currentPosition, PlaybackType? playbackType, String? playbackUrl, String? playSessionId, List? chapters, List? trickPlay, MediaSegmentsModel? mediaSegments, int? audioStreamIndex, int? subtitleStreamIndex, MediaStreamsModel? mediaStreamsModel, }) { return VideoStream( id: id ?? this.id, currentPosition: currentPosition ?? this.currentPosition, playbackType: playbackType ?? this.playbackType, playbackUrl: playbackUrl ?? this.playbackUrl, playSessionId: playSessionId ?? this.playSessionId, chapters: chapters ?? this.chapters, trickPlay: trickPlay ?? this.trickPlay, mediaSegments: mediaSegments ?? this.mediaSegments, audioStreamIndex: audioStreamIndex ?? this.audioStreamIndex, subtitleStreamIndex: subtitleStreamIndex ?? this.subtitleStreamIndex, mediaStreamsModel: mediaStreamsModel ?? this.mediaStreamsModel, ); } static VideoStream? fromPlayBackInfo(PlaybackInfoResponse info, Ref ref) { final mediaSource = info.mediaSources?.first; var playType = PlaybackType.directStream; String? playbackUrl; if (mediaSource == null) return null; if (mediaSource.supportsDirectStream ?? false) { final Map directOptions = { 'Static': 'true', 'mediaSourceId': mediaSource.id, 'api_key': ref.read(userProvider)?.credentials.token, }; if (mediaSource.eTag != null) { directOptions['Tag'] = mediaSource.eTag; } if (mediaSource.liveStreamId != null) { directOptions['LiveStreamId'] = mediaSource.liveStreamId; } final params = Uri(queryParameters: directOptions).query; playbackUrl = '${ref.read(userProvider)?.server ?? ""}/Videos/${mediaSource.id}/stream?$params'; } else if ((mediaSource.supportsTranscoding ?? false) && mediaSource.transcodingUrl != null) { playbackUrl = "${ref.read(userProvider)?.server ?? ""}${mediaSource.transcodingUrl ?? ""}"; playType = PlaybackType.transcode; } if (playbackUrl == null) return null; return VideoStream( id: info.mediaSources?.first.id ?? "", playbackUrl: playbackUrl, playbackType: playType, playSessionId: info.playSessionId ?? "", mediaStreamsModel: MediaStreamsModel.fromMediaStreamsList(info.mediaSources, ref), ); } }