diff --git a/lib/player/states/player.dart b/lib/player/states/player.dart index 7c3a0d49..b21770c8 100644 --- a/lib/player/states/player.dart +++ b/lib/player/states/player.dart @@ -317,38 +317,40 @@ class PlayerCubit extends Cubit { } } - playNext() { - EasyThrottle.throttle(skipToVideoThrottleName, const Duration(seconds: 1), () async { - if (settings.state.playerRepeatMode == PlayerRepeat.repeatOne) { - seek(Duration.zero); - play(); - } else if (state.videos.isNotEmpty || state.offlineVideos.isNotEmpty) { - //moving current video to played list - String? currentVideoId = state.currentlyPlaying?.videoId ?? state.offlineCurrentlyPlaying?.videoId; - if (currentVideoId != null) { - // state.playedVideos.remove(currentVideoId); - state.playedVideos.add(currentVideoId); - } + _playNextNow() async { + if (settings.state.playerRepeatMode == PlayerRepeat.repeatOne) { + seek(Duration.zero); + play(); + } else if (state.videos.isNotEmpty || state.offlineVideos.isNotEmpty) { + //moving current video to played list + String? currentVideoId = state.currentlyPlaying?.videoId ?? state.offlineCurrentlyPlaying?.videoId; + if (currentVideoId != null) { + // state.playedVideos.remove(currentVideoId); + state.playedVideos.add(currentVideoId); + } - if (state.playQueue.isNotEmpty) { - String toPlay = state.playQueue.removeFirst(); - if (state.videos.isNotEmpty) { - await switchToVideo(state.videos.firstWhere((element) => element.videoId == toPlay)); - } else { - await switchToOfflineVideo(state.offlineVideos.firstWhere((element) => element.videoId == toPlay)); - } - } else if (settings.state.playerRepeatMode == PlayerRepeat.repeatAll) { - state.playedVideos = []; - state.playQueue = ListQueue.from([]); - if (state.videos.isNotEmpty) { - await switchToVideo(state.videos[0]); - } else { - await switchToOfflineVideo(state.offlineVideos[0]); - } - generatePlayQueue(); + if (state.playQueue.isNotEmpty) { + String toPlay = state.playQueue.removeFirst(); + if (state.videos.isNotEmpty) { + await switchToVideo(state.videos.firstWhere((element) => element.videoId == toPlay)); + } else { + await switchToOfflineVideo(state.offlineVideos.firstWhere((element) => element.videoId == toPlay)); } + } else if (settings.state.playerRepeatMode == PlayerRepeat.repeatAll) { + state.playedVideos = []; + state.playQueue = ListQueue.from([]); + if (state.videos.isNotEmpty) { + await switchToVideo(state.videos[0]); + } else { + await switchToOfflineVideo(state.offlineVideos[0]); + } + generatePlayQueue(); } - }); + } + } + + playNext() { + EasyThrottle.throttle(skipToVideoThrottleName, const Duration(seconds: 1), _playNextNow); } playPrevious() { @@ -415,56 +417,96 @@ class PlayerCubit extends Cubit { } } - _switchToVideo(IdedVideo video, {Duration? startAt}) async { - // we move the existing video to the stack of played video + /// skip to queue video of index + /// if we're not shuffling, we also rebuild the playnext and played previously queue + skipToVideo(int index) { + if (index < 0 || index >= state.videos.length) { + return; + } - bool isOffline = video is DownloadedVideo; - // we want to switch to audio mode as soon as we can to prevent problems when switching from audio to video or the other way - if (isOffline) { - setAudio(video.audioOnly); + if (settings.state.playerShuffleMode) { + } else { + List played = []; + List playNext = []; + for (int i = 0; i < state.videos.length; i++) { + var v = state.videos[i]; + if (i < index) { + played.add(v.videoId); + } else if (i > index) { + playNext.add(v.videoId); + } + } + emit(state.copyWith(playedVideos: played, playQueue: ListQueue.from(playNext))); } + switchToVideo(state.videos[index]); + } + /// Switches to a video without changing the queue + _switchToVideo(IdedVideo video, {Duration? startAt}) async { var state = this.state.copyWith(); + try { + // we move the existing video to the stack of played video - state.mediaEvent = MediaEvent(state: MediaState.loading); + bool isOffline = video is DownloadedVideo; + // we want to switch to audio mode as soon as we can to prevent problems when switching from audio to video or the other way + if (isOffline) { + setAudio(video.audioOnly); + } - if (isOffline) { - state.videos = []; - state.currentlyPlaying = null; - } else { - state.offlineVideos = []; - state.offlineCurrentlyPlaying = null; - } - List toCheck = isOffline ? state.offlineVideos : state.videos; + state.mediaEvent = MediaEvent(state: MediaState.loading); - emit(state); - state = this.state.copyWith(); + if (isOffline) { + state.videos = []; + state.currentlyPlaying = null; + } else { + state.offlineVideos = []; + state.offlineCurrentlyPlaying = null; + } + + List toCheck = isOffline ? state.offlineVideos : state.videos; + + emit(state); + state = this.state.copyWith(); - if (!isOffline) { - late Video v; - if (video is Video) { - v = video; + if (!isOffline) { + late Video v; + if (video is Video) { + v = video; + } else { + v = await service.getVideo(video.videoId); + } + state.currentlyPlaying = v; + state.mediaCommand = MediaCommand(MediaCommandType.switchVideo, value: SwitchVideoValue(video: v, startAt: startAt)); } else { - v = await service.getVideo(video.videoId); + state.offlineCurrentlyPlaying = video; + state.mediaCommand = MediaCommand(MediaCommandType.switchToOfflineVideo, value: video); } - state.currentlyPlaying = v; - state.mediaCommand = MediaCommand(MediaCommandType.switchVideo, value: SwitchVideoValue(video: v, startAt: startAt)); - } else { - state.offlineCurrentlyPlaying = video; - state.mediaCommand = MediaCommand(MediaCommandType.switchToOfflineVideo, value: video); - } - state.position = Duration.zero; - state.forwardStep = defaultStep; - state.rewindStep = defaultStep; + state.position = Duration.zero; + state.forwardStep = defaultStep; + state.rewindStep = defaultStep; - emit(state); + emit(state); - setSponsorBlock(); + await setSponsorBlock(); - if (!isTv) { - mediaHandler.skipToQueueItem(currentIndex); + if (!isTv) { + mediaHandler.skipToQueueItem(currentIndex); + } + } catch (err) { + emit(state); + if(state.videos.length == 1) { + // if we can't get video details, we need to stop everything + log.severe("Couldn't play video '${video.videoId}', stopping player to avoid app crash"); + hide(); + }else{ + // if we have more than 1 video + log.severe("Couldn't play video '${video.videoId}', removing it from the queue"); + + removeVideoFromQueue(video.videoId); + _playNextNow(); + } } } @@ -560,7 +602,7 @@ class PlayerCubit extends Cubit { var movedItem = listToUpdate.removeAt(oldItemIndex); listToUpdate.insert(newItemIndex, movedItem); log.fine('Reordered list: $oldItemIndex new index: ${listToUpdate.indexOf(movedItem)}'); - if(newItemIndex <= currentIndex){ + if (newItemIndex <= currentIndex) { state.playedVideos.add(listToUpdate[newItemIndex].videoId); } /* @@ -589,7 +631,7 @@ class PlayerCubit extends Cubit { } setSponsorBlock() async { - var state = this.state.copyWith(); + List> newSegments = []; if (state.currentlyPlaying != null) { List types = SponsorSegmentType.values.where((e) => db.getSettings(e.settingsName())?.value == 'true').toList(); @@ -602,15 +644,11 @@ class PlayerCubit extends Cubit { return segment; })); - state.sponsorSegments = segments; + newSegments = segments; log.fine('we found ${segments.length} segments to skip'); - } else { - state.sponsorSegments = []; } - } else { - state.sponsorSegments = []; } - emit(state); + emit(state.copyWith(sponsorSegments: newSegments)); } seek(Duration duration) { @@ -749,9 +787,8 @@ class PlayerCubit extends Cubit { videos.shuffle(); } - state.playQueue = ListQueue.from(videos); - - // set up the queue + ListQueue playQueue = ListQueue.from(videos); + emit(state.copyWith(playQueue: playQueue)); } } diff --git a/lib/player/views/components/video_queue.dart b/lib/player/views/components/video_queue.dart index c4fb712a..967efff2 100644 --- a/lib/player/views/components/video_queue.dart +++ b/lib/player/views/components/video_queue.dart @@ -34,7 +34,7 @@ class VideoQueue extends StatelessWidget { )) ], child: CompactVideo( - onTap: isPlaying ? () {} : () => controller.switchToVideo(video), + onTap: isPlaying ? () {} : () => controller.skipToVideo(index), video: video, highlighted: isPlaying, ), @@ -65,7 +65,7 @@ class VideoQueue extends StatelessWidget { )) ], child: CompactVideo( - onTap: isPlaying ? () {} : () => controller.switchToOfflineVideo(v), + onTap: isPlaying ? () {} : () => controller.skipToVideo(index), offlineVideo: v, highlighted: isPlaying, trailing: [ diff --git a/lib/utils/video_post_processing.dart b/lib/utils/video_post_processing.dart index d0fc60ff..f7045ca1 100644 --- a/lib/utils/video_post_processing.dart +++ b/lib/utils/video_post_processing.dart @@ -6,11 +6,13 @@ import 'package:logging/logging.dart'; import '../videos/models/base_video.dart'; var log = Logger('Video post process'); +const privateVideoString = "[Private video]"; Future> postProcessVideos(List toProcess) async { try { int start = DateTime.now().millisecondsSinceEpoch; - List videos = toProcess ?? []; + List videos = toProcess.where((element) => element.title != privateVideoString).toList() ?? []; + videos = await VideoFilter.filterVideos(videos); videos = await DeArrow.processVideos(videos); diff --git a/lib/videos/models/dearrow.dart b/lib/videos/models/dearrow.dart index 84feb493..12df2726 100644 --- a/lib/videos/models/dearrow.dart +++ b/lib/videos/models/dearrow.dart @@ -50,13 +50,12 @@ class DeArrow { if (!doThumbnails) return; if (cache.url != null) { - - bool isThumbnailAvailable = await service.testDeArrowThumbnail(cache.url); - if (isThumbnailAvailable) { - video.deArrowThumbnailUrl = cache.url; - // if we've set both things from cache, we stop otherwise we go through normal process - return; - } + // bool isThumbnailAvailable = await service.testDeArrowThumbnail(cache.url); + // if (isThumbnailAvailable) { + video.deArrowThumbnailUrl = cache.url; + // if we've set both things from cache, we stop otherwise we go through normal process + return; + // } } } diff --git a/pubspec.yaml b/pubspec.yaml index a07d94b8..865f605f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.17.0+4034 +version: 1.17.1+4035 environment: sdk: '>=3.0.0 <4.0.0'