Skip to content

Commit

Permalink
add sleep timer for videos
Browse files Browse the repository at this point in the history
fix #603
  • Loading branch information
lamarios committed Sep 28, 2024
1 parent 378f5a9 commit 64fd8bf
Show file tree
Hide file tree
Showing 14 changed files with 534 additions and 30 deletions.
7 changes: 6 additions & 1 deletion lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -1374,5 +1374,10 @@
"serverUnreachable": "Server is unreachable, or is not a valid invidious server",
"copyToDownloadFolder": "Copy to download folder",
"fileCopiedToDownloadFolder": "File copied to download folder",
"videoDeleted": "Video deleted"
"videoDeleted": "Video deleted",
"sleepTimer": "Sleep timer",
"stopTheVideo": "Stop the video",
"stopTheVideoExplanation": "If enabled, the video will be closed, if disabled the video will be simply paused",
"setTimer": "Set timer",
"cancelSleepTimer": "Cancel sleep timer"
}
11 changes: 11 additions & 0 deletions lib/player/models/sleep_timer.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import 'package:freezed_annotation/freezed_annotation.dart';

part 'sleep_timer.freezed.dart';

@freezed
class SleepTimer with _$SleepTimer {
const factory SleepTimer({
@Default(Duration(minutes: 5)) Duration duration,
@Default(true) bool stopVideo,
}) = _SleepTimer;
}
165 changes: 165 additions & 0 deletions lib/player/models/sleep_timer.freezed.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark

part of 'sleep_timer.dart';

// **************************************************************************
// FreezedGenerator
// **************************************************************************

T _$identity<T>(T value) => value;

final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');

/// @nodoc
mixin _$SleepTimer {
Duration get duration => throw _privateConstructorUsedError;
bool get stopVideo => throw _privateConstructorUsedError;

/// Create a copy of SleepTimer
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$SleepTimerCopyWith<SleepTimer> get copyWith =>
throw _privateConstructorUsedError;
}

/// @nodoc
abstract class $SleepTimerCopyWith<$Res> {
factory $SleepTimerCopyWith(
SleepTimer value, $Res Function(SleepTimer) then) =
_$SleepTimerCopyWithImpl<$Res, SleepTimer>;
@useResult
$Res call({Duration duration, bool stopVideo});
}

/// @nodoc
class _$SleepTimerCopyWithImpl<$Res, $Val extends SleepTimer>
implements $SleepTimerCopyWith<$Res> {
_$SleepTimerCopyWithImpl(this._value, this._then);

// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;

/// Create a copy of SleepTimer
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? duration = null,
Object? stopVideo = null,
}) {
return _then(_value.copyWith(
duration: null == duration
? _value.duration
: duration // ignore: cast_nullable_to_non_nullable
as Duration,
stopVideo: null == stopVideo
? _value.stopVideo
: stopVideo // ignore: cast_nullable_to_non_nullable
as bool,
) as $Val);
}
}

/// @nodoc
abstract class _$$SleepTimerImplCopyWith<$Res>
implements $SleepTimerCopyWith<$Res> {
factory _$$SleepTimerImplCopyWith(
_$SleepTimerImpl value, $Res Function(_$SleepTimerImpl) then) =
__$$SleepTimerImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({Duration duration, bool stopVideo});
}

/// @nodoc
class __$$SleepTimerImplCopyWithImpl<$Res>
extends _$SleepTimerCopyWithImpl<$Res, _$SleepTimerImpl>
implements _$$SleepTimerImplCopyWith<$Res> {
__$$SleepTimerImplCopyWithImpl(
_$SleepTimerImpl _value, $Res Function(_$SleepTimerImpl) _then)
: super(_value, _then);

/// Create a copy of SleepTimer
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? duration = null,
Object? stopVideo = null,
}) {
return _then(_$SleepTimerImpl(
duration: null == duration
? _value.duration
: duration // ignore: cast_nullable_to_non_nullable
as Duration,
stopVideo: null == stopVideo
? _value.stopVideo
: stopVideo // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}

/// @nodoc
class _$SleepTimerImpl implements _SleepTimer {
const _$SleepTimerImpl(
{this.duration = const Duration(minutes: 5), this.stopVideo = true});

@override
@JsonKey()
final Duration duration;
@override
@JsonKey()
final bool stopVideo;

@override
String toString() {
return 'SleepTimer(duration: $duration, stopVideo: $stopVideo)';
}

@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$SleepTimerImpl &&
(identical(other.duration, duration) ||
other.duration == duration) &&
(identical(other.stopVideo, stopVideo) ||
other.stopVideo == stopVideo));
}

@override
int get hashCode => Object.hash(runtimeType, duration, stopVideo);

/// Create a copy of SleepTimer
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$SleepTimerImplCopyWith<_$SleepTimerImpl> get copyWith =>
__$$SleepTimerImplCopyWithImpl<_$SleepTimerImpl>(this, _$identity);
}

abstract class _SleepTimer implements SleepTimer {
const factory _SleepTimer({final Duration duration, final bool stopVideo}) =
_$SleepTimerImpl;

@override
Duration get duration;
@override
bool get stopVideo;

/// Create a copy of SleepTimer
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$SleepTimerImplCopyWith<_$SleepTimerImpl> get copyWith =>
throw _privateConstructorUsedError;
}
22 changes: 22 additions & 0 deletions lib/player/states/player.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:audio_service/audio_service.dart';
import 'package:audio_session/audio_session.dart';
import 'package:back_button_interceptor/back_button_interceptor.dart';
import 'package:bloc/bloc.dart';
import 'package:clipious/player/models/sleep_timer.dart';
import 'package:easy_debounce/easy_debounce.dart';
import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart';
Expand Down Expand Up @@ -965,6 +966,26 @@ class PlayerCubit extends Cubit<PlayerState> with WidgetsBindingObserver {
setFullScreen(FullScreenState.fullScreen);
}
}

void sleep(SleepTimer sleepTimer) {
emit(state.copyWith(hasTimer: true));
EasyDebounce.debounce(
'video-sleep-timer',
sleepTimer.duration,
() {
emit(state.copyWith(hasTimer: false));
pause();
if (sleepTimer.stopVideo && !isClosed) {
hide();
}
},
);
}

void cancelSleep() {
emit(state.copyWith(hasTimer: false));
EasyDebounce.cancel('video-sleep-timer');
}
}

@freezed
Expand All @@ -973,6 +994,7 @@ class PlayerState with _$PlayerState {
{
// player display properties
@Default(true) bool isMini,
@Default(false) bool hasTimer,
double? top,
@Default(false) bool isDragging,
@Default(0) int selectedFullScreenIndex,
Expand Down
25 changes: 24 additions & 1 deletion lib/player/states/player.freezed.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ final _privateConstructorUsedError = UnsupportedError(
mixin _$PlayerState {
// player display properties
bool get isMini => throw _privateConstructorUsedError;
bool get hasTimer => throw _privateConstructorUsedError;
double? get top => throw _privateConstructorUsedError;
bool get isDragging => throw _privateConstructorUsedError;
int get selectedFullScreenIndex => throw _privateConstructorUsedError;
Expand Down Expand Up @@ -76,6 +77,7 @@ abstract class $PlayerStateCopyWith<$Res> {
@useResult
$Res call(
{bool isMini,
bool hasTimer,
double? top,
bool isDragging,
int selectedFullScreenIndex,
Expand Down Expand Up @@ -129,6 +131,7 @@ class _$PlayerStateCopyWithImpl<$Res, $Val extends PlayerState>
@override
$Res call({
Object? isMini = null,
Object? hasTimer = null,
Object? top = freezed,
Object? isDragging = null,
Object? selectedFullScreenIndex = null,
Expand Down Expand Up @@ -170,6 +173,10 @@ class _$PlayerStateCopyWithImpl<$Res, $Val extends PlayerState>
? _value.isMini
: isMini // ignore: cast_nullable_to_non_nullable
as bool,
hasTimer: null == hasTimer
? _value.hasTimer
: hasTimer // ignore: cast_nullable_to_non_nullable
as bool,
top: freezed == top
? _value.top
: top // ignore: cast_nullable_to_non_nullable
Expand Down Expand Up @@ -324,6 +331,7 @@ abstract class _$$PlayerStateImplCopyWith<$Res>
@useResult
$Res call(
{bool isMini,
bool hasTimer,
double? top,
bool isDragging,
int selectedFullScreenIndex,
Expand Down Expand Up @@ -375,6 +383,7 @@ class __$$PlayerStateImplCopyWithImpl<$Res>
@override
$Res call({
Object? isMini = null,
Object? hasTimer = null,
Object? top = freezed,
Object? isDragging = null,
Object? selectedFullScreenIndex = null,
Expand Down Expand Up @@ -416,6 +425,10 @@ class __$$PlayerStateImplCopyWithImpl<$Res>
? _value.isMini
: isMini // ignore: cast_nullable_to_non_nullable
as bool,
hasTimer: null == hasTimer
? _value.hasTimer
: hasTimer // ignore: cast_nullable_to_non_nullable
as bool,
top: freezed == top
? _value.top
: top // ignore: cast_nullable_to_non_nullable
Expand Down Expand Up @@ -559,6 +572,7 @@ class __$$PlayerStateImplCopyWithImpl<$Res>
class _$PlayerStateImpl extends _PlayerState {
const _$PlayerStateImpl(
{this.isMini = true,
this.hasTimer = false,
this.top,
this.isDragging = false,
this.selectedFullScreenIndex = 0,
Expand Down Expand Up @@ -605,6 +619,9 @@ class _$PlayerStateImpl extends _PlayerState {
@JsonKey()
final bool isMini;
@override
@JsonKey()
final bool hasTimer;
@override
final double? top;
@override
@JsonKey()
Expand Down Expand Up @@ -740,7 +757,7 @@ class _$PlayerStateImpl extends _PlayerState {

@override
String toString() {
return 'PlayerState(isMini: $isMini, top: $top, isDragging: $isDragging, selectedFullScreenIndex: $selectedFullScreenIndex, isHidden: $isHidden, isClosing: $isClosing, dragDistance: $dragDistance, showMiniPlaceholder: $showMiniPlaceholder, dragStartMini: $dragStartMini, height: $height, fullScreenState: $fullScreenState, muted: $muted, aspectRatio: $aspectRatio, currentlyPlaying: $currentlyPlaying, offlineCurrentlyPlaying: $offlineCurrentlyPlaying, videos: $videos, offlineVideos: $offlineVideos, playedVideos: $playedVideos, playQueue: $playQueue, isAudio: $isAudio, isPip: $isPip, offset: $offset, startAt: $startAt, position: $position, bufferedPosition: $bufferedPosition, isPlaying: $isPlaying, speed: $speed, mediaCommand: $mediaCommand, mediaEvent: $mediaEvent, sponsorSegments: $sponsorSegments, nextSegment: $nextSegment, forwardStep: $forwardStep, rewindStep: $rewindStep, totalFastForward: $totalFastForward, totalRewind: $totalRewind, orientation: $orientation)';
return 'PlayerState(isMini: $isMini, hasTimer: $hasTimer, top: $top, isDragging: $isDragging, selectedFullScreenIndex: $selectedFullScreenIndex, isHidden: $isHidden, isClosing: $isClosing, dragDistance: $dragDistance, showMiniPlaceholder: $showMiniPlaceholder, dragStartMini: $dragStartMini, height: $height, fullScreenState: $fullScreenState, muted: $muted, aspectRatio: $aspectRatio, currentlyPlaying: $currentlyPlaying, offlineCurrentlyPlaying: $offlineCurrentlyPlaying, videos: $videos, offlineVideos: $offlineVideos, playedVideos: $playedVideos, playQueue: $playQueue, isAudio: $isAudio, isPip: $isPip, offset: $offset, startAt: $startAt, position: $position, bufferedPosition: $bufferedPosition, isPlaying: $isPlaying, speed: $speed, mediaCommand: $mediaCommand, mediaEvent: $mediaEvent, sponsorSegments: $sponsorSegments, nextSegment: $nextSegment, forwardStep: $forwardStep, rewindStep: $rewindStep, totalFastForward: $totalFastForward, totalRewind: $totalRewind, orientation: $orientation)';
}

@override
Expand All @@ -749,6 +766,8 @@ class _$PlayerStateImpl extends _PlayerState {
(other.runtimeType == runtimeType &&
other is _$PlayerStateImpl &&
(identical(other.isMini, isMini) || other.isMini == isMini) &&
(identical(other.hasTimer, hasTimer) ||
other.hasTimer == hasTimer) &&
(identical(other.top, top) || other.top == top) &&
(identical(other.isDragging, isDragging) ||
other.isDragging == isDragging) &&
Expand Down Expand Up @@ -817,6 +836,7 @@ class _$PlayerStateImpl extends _PlayerState {
int get hashCode => Object.hashAll([
runtimeType,
isMini,
hasTimer,
top,
isDragging,
selectedFullScreenIndex,
Expand Down Expand Up @@ -866,6 +886,7 @@ class _$PlayerStateImpl extends _PlayerState {
abstract class _PlayerState extends PlayerState {
const factory _PlayerState(
{final bool isMini,
final bool hasTimer,
final double? top,
final bool isDragging,
final int selectedFullScreenIndex,
Expand Down Expand Up @@ -907,6 +928,8 @@ abstract class _PlayerState extends PlayerState {
@override
bool get isMini;
@override
bool get hasTimer;
@override
double? get top;
@override
bool get isDragging;
Expand Down
18 changes: 18 additions & 0 deletions lib/player/states/sleep_timer.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import 'package:clipious/player/models/sleep_timer.dart';
import 'package:clipious/player/views/components/sleep_timer.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class SleepTimerCubit extends Cubit<SleepTimer> {
SleepTimerCubit(super.initialState);

setDuration(Duration duration) {
if (duration >= sleepTimerMinDuration &&
duration <= sleepTimerMaxDuration) {
emit(state.copyWith(duration: duration));
}
}

setStopVideo(bool stop) {
emit(state.copyWith(stopVideo: stop));
}
}
8 changes: 8 additions & 0 deletions lib/player/states/tv_player_controls.dart
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,14 @@ class TvPlayerControlsCubit extends Cubit<TvPlayerControlsState> {
return KeyEventResult.ignored;
}

hideSettings() {
emit(state.copyWith(
controlsOpacity: 0,
showSettings: false,
showQueue: false,
displayControls: false));
}

hideControls() {
EasyDebounce.debounce('tv-controls', controlFadeOut, () {
if (!isClosed) {
Expand Down
Loading

0 comments on commit 64fd8bf

Please sign in to comment.