Skip to content

Commit fa2c5b6

Browse files
committed
rewrite: Integrated AniSkip
1 parent 7a1f5bf commit fa2c5b6

21 files changed

Lines changed: 948 additions & 1214 deletions

lib/application/cubits/video_cubit.dart

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,23 @@ import 'package:unyo/core/notification/extension_notifier.dart';
1919
import 'package:unyo/core/notification/media_list_entry_notifier.dart';
2020
import 'package:unyo/core/notification/user_notifier.dart';
2121
import 'package:unyo/core/notification/video_info_notifier.dart';
22+
import 'package:unyo/core/services/api/dto/aniskip/aniskip_times_entity.dart';
23+
import 'package:unyo/core/services/api/http/api_response.dart';
24+
import 'package:unyo/core/services/api/http/http_service.dart';
2225
import 'package:unyo/core/services/video/video_service.dart';
2326
import 'package:unyo/data/repositories/extension_repository_aniyomi.dart';
2427
import 'package:unyo/domain/entities/anime.dart';
2528
import 'package:unyo/domain/entities/episode_info.dart';
2629
import 'package:unyo/domain/entities/extension.dart';
2730
import 'package:unyo/domain/entities/extension/video.dart' as ext;
31+
import 'package:unyo/config/config.dart' as config;
2832
import 'package:unyo/domain/entities/media_list_entry.dart';
2933
import 'package:unyo/domain/entities/user.dart';
3034
import 'package:unyo/core/di/locator.dart';
3135
import 'package:unyo/domain/entities/video_info.dart';
3236
import 'package:unyo/presentation/dialogs/warning_dialog.dart';
3337

3438
class VideoCubit extends Cubit<VideoState> with EffectMixin<VideoState> {
35-
final Logger _logger = sl<Logger>();
3639

3740
// Repositories
3841
final ExtensionRepositoryAniyomi _extensionRepositoryAniyomi;
@@ -55,6 +58,8 @@ class VideoCubit extends Cubit<VideoState> with EffectMixin<VideoState> {
5558

5659
// Services
5760
late VideoService _videoService;
61+
final Logger _logger = sl<Logger>();
62+
final HttpService _httpService = sl<HttpService>();
5863

5964
// Others
6065
bool _videoServiceInitialized = false;
@@ -78,6 +83,8 @@ class VideoCubit extends Cubit<VideoState> with EffectMixin<VideoState> {
7883
episodesInfo: [],
7984
mediaListEntry: MediaListEntryModel.empty(),
8085
availableCastDevices: [],
86+
openingSkipTimes: AniskipTimesResults(),
87+
endingSkipTimes: AniskipTimesResults(),
8188
isLoading: true,
8289
),
8390
) {
@@ -117,6 +124,7 @@ class VideoCubit extends Cubit<VideoState> with EffectMixin<VideoState> {
117124
_videoInfoSubscription = _videoInfoNotifier.videoInfoStream.listen((videoInfo) {
118125
_initializeVideoService(videoInfo);
119126
_videoInfoSubscription.cancel();
127+
Future.delayed(const Duration(seconds: 1), () => _getAniskipSkipTimes(state.selectedAnime, videoInfo));
120128
});
121129
_selectedAnimeSubscription = _selectedAnimeNotifier.animeStream.listen((selectedAnime) {
122130
emit(state.copyWith(selectedAnime: selectedAnime));
@@ -168,6 +176,52 @@ class VideoCubit extends Cubit<VideoState> with EffectMixin<VideoState> {
168176
_logger.i("VideoService updated for new episode.");
169177
}
170178

179+
Future<void> _getAniskipSkipTimes(Anime selectedAnime, VideoInfo videoInfo) async {
180+
double formattedDuration = selectedAnime.duration > 0 ? selectedAnime.duration * 60 : 24 * 60;
181+
ApiResponse<AniskipTimesEntity> aniskipResponse =
182+
await _httpService.get(
183+
"${config.aniskiBaseEndpoint}/v2/skip-times/${selectedAnime.idMal}/${videoInfo.playlistIndex + 1}?types=op&types=ed&episodeLength=$formattedDuration",
184+
fromJson: AniskipTimesEntity.fromJson
185+
);
186+
if (aniskipResponse.statusCode > 299 || aniskipResponse.data.statusCode > 299) {
187+
_logger.e("Failed to fetch Aniskip times for malId: ${selectedAnime.idMal}. Status code: ${aniskipResponse.data.statusCode}. Message: ${aniskipResponse.data.message}");
188+
return;
189+
}
190+
final openingSkipTimes = aniskipResponse.data.results.where((skip) => skip.skipType == "op").firstOrNull;
191+
final endingSkipTimes = aniskipResponse.data.results.where((skip) => skip.skipType == "ed").firstOrNull;
192+
emit(state.copyWith(openingSkipTimes: openingSkipTimes ?? AniskipTimesResults(), endingSkipTimes: endingSkipTimes ?? AniskipTimesResults()));
193+
}
194+
195+
String getSkipTimeText() {
196+
if (state.openingSkipTimes.interval.endTime > 0 &&
197+
_videoService.position.inSeconds > state.openingSkipTimes.interval.startTime &&
198+
state.openingSkipTimes.interval.endTime > _videoService.position.inSeconds) {
199+
return "Skip Opening";
200+
} else if (state.endingSkipTimes.interval.endTime > 0 &&
201+
_videoService.position.inSeconds > state.endingSkipTimes.interval.startTime &&
202+
state.endingSkipTimes.interval.endTime > _videoService.position.inSeconds) {
203+
return "Skip Ending";
204+
} else {
205+
return "+ ${state.loggedUser.settings.manualSkipTime.toString()}s";
206+
}
207+
}
208+
209+
210+
void performSkipActin() {
211+
if (state.openingSkipTimes.interval.endTime > 0 &&
212+
_videoService.position.inSeconds > state.openingSkipTimes.interval.startTime &&
213+
state.openingSkipTimes.interval.endTime > _videoService.position.inSeconds) {
214+
_videoService.seekTo(Duration(seconds: state.openingSkipTimes.interval.endTime.toInt()));
215+
} else if (state.endingSkipTimes.interval.endTime > 0 &&
216+
_videoService.position.inSeconds > state.endingSkipTimes.interval.startTime &&
217+
state.endingSkipTimes.interval.endTime > _videoService.position.inSeconds) {
218+
_videoService.seekTo(Duration(seconds: state.endingSkipTimes.interval.endTime.toInt()));
219+
} else {
220+
_videoService.forward(Duration(seconds: state.loggedUser.settings.manualSkipTime));
221+
}
222+
}
223+
224+
171225
Future<void> _getAvailableCastDevices() async {
172226
List<CastDevice> availableCastDevices = await CastDiscoveryService().search();
173227
emit(state.copyWith(availableCastDevices: availableCastDevices));

lib/application/states/video_state.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
33
import 'package:k3vinb5_aniyomi_bridge/jmodels/jsepisode.dart';
44
import 'package:unyo/application/cubits/effect_mixin.dart';
55
import 'package:unyo/application/effects/app_effects.dart';
6+
import 'package:unyo/core/services/api/dto/aniskip/aniskip_times_entity.dart';
67
import 'package:unyo/domain/entities/anime.dart';
78
import 'package:unyo/domain/entities/episode_info.dart';
89
import 'package:unyo/domain/entities/extension.dart';
@@ -23,6 +24,8 @@ abstract class VideoState with _$VideoState implements HasEffects{
2324
required List<JSEpisode> extensionEpisodeResults,
2425
required MediaListEntry mediaListEntry,
2526
required List<CastDevice> availableCastDevices,
27+
required AniskipTimesResults openingSkipTimes,
28+
required AniskipTimesResults endingSkipTimes,
2629
required bool isLoading,
2730
@Default(<AppEffect>[]) List<AppEffect> effects,
2831
}) = _VideoState;

0 commit comments

Comments
 (0)