Skip to content

Commit

Permalink
Add buffer bar, theme video player (#715)
Browse files Browse the repository at this point in the history
* Add buffer bar, theme video player

* Reuse controls

* Improve video controls
  • Loading branch information
Feichtmeier authored May 8, 2024
1 parent 5f42896 commit 8ce1402
Show file tree
Hide file tree
Showing 14 changed files with 271 additions and 86 deletions.
10 changes: 10 additions & 0 deletions lib/src/common/icons.dart
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,16 @@ class Iconz {
: appleStyled
? CupertinoIcons.volume_off
: Icons.volume_off;
IconData get fullWindowExit => yaruStyled
? YaruIcons.arrow_down
: appleStyled
? CupertinoIcons.arrow_down
: Icons.arrow_downward;
IconData get fullWindow => yaruStyled
? YaruIcons.arrow_up
: appleStyled
? CupertinoIcons.arrow_up
: Icons.arrow_upward;
IconData get fullScreenExit => yaruStyled
? YaruIcons.fullscreen_exit
: appleStyled
Expand Down
2 changes: 2 additions & 0 deletions lib/src/l10n/app_de.arb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
"rewind10": "10 Sekunden zurückspulen",
"fullWindow": "Vollfenstermodus",
"leaveFullWindow": "Vollfenstermodus verlassen",
"fullScreen": "Vollbildmodus",
"leaveFullScreen": "Vollbildmodus verlassen",
"playbackRate": "Wiedergabegeschwindigkeit",
"addToFavorites": "Zu Favoriten hinzufügen",
"removeFromFavorites": "Aus Favoriten entfernen",
Expand Down
2 changes: 2 additions & 0 deletions lib/src/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
"rewind10": "Rewind 10 seconds",
"fullWindow": "Enter full window mode",
"leaveFullWindow": "Leave full window mode",
"fullScreen": "Enter full screen mode",
"leaveFullScreen": "Leave full screen mode",
"playbackRate": "Playback rate",
"addToFavorites": "Add to favorites",
"removeFromFavorites": "Remove from favorites",
Expand Down
2 changes: 2 additions & 0 deletions lib/src/player/player_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ class PlayerModel extends SafeChangeNotifier {
Duration? get position => _service.position;
void setPosition(Duration? value) => _service.setPosition(value);

Duration? get buffer => _service.buffer;

Future<void> seekInSeconds(int seconds) async {
if (position != null && position!.inSeconds + seconds >= 0) {
setPosition(
Expand Down
16 changes: 16 additions & 0 deletions lib/src/player/player_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class PlayerService {
StreamSubscription<bool>? _isPlayingSub;
StreamSubscription<Duration>? _durationSub;
StreamSubscription<Duration>? _positionSub;
StreamSubscription<Duration>? _bufferSub;
StreamSubscription<bool>? _isCompletedSub;
StreamSubscription<double>? _volumeSub;
StreamSubscription<Tracks>? _tracksSub;
Expand Down Expand Up @@ -149,6 +150,16 @@ class PlayerService {
_setMediaControlPosition(value);
}

final _bufferController = StreamController<bool>.broadcast();
Stream<bool> get bufferChanged => _bufferController.stream;
Duration? _buffer = Duration.zero;
Duration? get buffer => _buffer;
void setBuffer(Duration? value) {
if (buffer?.inSeconds == value?.inSeconds) return;
_buffer = value;
_bufferController.add(true);
}

final _repeatSingleController = StreamController<bool>.broadcast();
Stream<bool> get repeatSingleChanged => _repeatSingleController.stream;
bool _repeatSingle = false;
Expand Down Expand Up @@ -262,6 +273,10 @@ class PlayerService {
setPosition(newPosition);
});

_bufferSub = _player.stream.buffer.listen((event) {
setBuffer(event);
});

_isCompletedSub = _player.stream.completed.listen((value) async {
if (value) {
await playNext();
Expand Down Expand Up @@ -770,6 +785,7 @@ class PlayerService {
await _rateSub?.cancel();
await _rateController.close();
await _radioHistoryController.close();
await _bufferSub?.cancel();

await _player.dispose();
}
Expand Down
2 changes: 1 addition & 1 deletion lib/src/player/view/bottom_player.dart
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ class BottomPlayer extends StatelessWidget with WatchItMixin {
IconButton(
tooltip: context.l10n.fullWindow,
icon: Icon(
Iconz().fullScreen,
Iconz().fullWindow,
color: theme.colorScheme.onSurface,
),
onPressed: () => appModel.setFullScreen(true),
Expand Down
51 changes: 25 additions & 26 deletions lib/src/player/view/full_height_player.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:media_kit_video/media_kit_video.dart';
import 'package:yaru/yaru.dart';

import '../../../app.dart';
Expand All @@ -11,6 +10,7 @@ import 'blurred_full_height_player_image.dart';
import 'full_height_player_image.dart';
import 'full_height_player_top_controls.dart';
import 'full_height_title_and_artist.dart';
import 'full_height_video_player.dart';
import 'player_main_controls.dart';
import 'player_track.dart';
import 'up_next_bubble.dart';
Expand All @@ -36,22 +36,20 @@ class FullHeightPlayer extends StatelessWidget with WatchItMixin {
final showUpNextBubble = notAlone &&
nextAudio?.title != null &&
nextAudio?.artist != null &&
!isVideo &&
size.width > 600;
final model = getIt<PlayerModel>();
final active = audio?.path != null || isOnline;
final iconColor = isVideo ? Colors.white : theme.colorScheme.onSurface;

final bodyWithControls = Stack(
alignment: Alignment.topRight,
children: [
if (isVideo)
RepaintBoundary(
child: Video(
controller: model.controller,
),
)
else
final Widget bodyWithControls;
if (isVideo) {
bodyWithControls = FullHeightVideoPlayer(
playerViewMode: playerViewMode,
);
} else {
bodyWithControls = Stack(
alignment: Alignment.topRight,
children: [
Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 35),
Expand Down Expand Up @@ -89,21 +87,22 @@ class FullHeightPlayer extends StatelessWidget with WatchItMixin {
),
),
),
FullHeightPlayerTopControls(
iconColor: iconColor,
playerViewMode: playerViewMode,
),
if (showUpNextBubble)
Positioned(
left: 10,
bottom: 10,
child: UpNextBubble(
audio: audio,
nextAudio: nextAudio,
),
FullHeightPlayerTopControls(
iconColor: iconColor,
playerViewMode: playerViewMode,
),
],
);
if (showUpNextBubble)
Positioned(
left: 10,
bottom: 10,
child: UpNextBubble(
audio: audio,
nextAudio: nextAudio,
),
),
],
);
}

final body = isMobile
? GestureDetector(
Expand Down
92 changes: 44 additions & 48 deletions lib/src/player/view/full_height_player_top_controls.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ class FullHeightPlayerTopControls extends StatelessWidget with WatchItMixin {
super.key,
required this.iconColor,
required this.playerViewMode,
this.padding,
});

final Color iconColor;
final PlayerViewMode playerViewMode;
final EdgeInsetsGeometry? padding;

@override
Widget build(BuildContext context) {
Expand All @@ -33,66 +35,60 @@ class FullHeightPlayerTopControls extends StatelessWidget with WatchItMixin {
(PlayerModel m) =>
m.queue.length > 1 || audio?.audioType == AudioType.local,
);
final isVideo = watchPropertyValue((PlayerModel m) => m.isVideo == true);
final playerToTheRight = context.m.size.width > kSideBarThreshHold;
final fullScreen = watchPropertyValue((AppModel m) => m.fullScreen);
final appModel = getIt<AppModel>();
final isOnline = watchPropertyValue((PlayerModel m) => m.isOnline);
final active = audio?.path != null || isOnline;

return Padding(
padding: EdgeInsets.only(
right: kYaruPagePadding,
top: Platform.isMacOS ? 0 : kYaruPagePadding,
),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100),
color: isVideo ? Colors.black.withOpacity(0.6) : null,
),
child: Wrap(
alignment: WrapAlignment.end,
spacing: 5.0,
children: [
if (audio?.audioType != AudioType.podcast)
PlayerLikeIcon(
audio: audio,
color: iconColor,
),
if (showQueueButton) QueueButton(color: iconColor),
ShareButton(
padding: padding ??
EdgeInsets.only(
right: kYaruPagePadding,
top: Platform.isMacOS ? 0 : kYaruPagePadding,
),
child: Wrap(
alignment: WrapAlignment.end,
spacing: 5.0,
children: [
if (audio?.audioType != AudioType.podcast)
PlayerLikeIcon(
audio: audio,
color: iconColor,
),
if (showQueueButton) QueueButton(color: iconColor),
ShareButton(
audio: audio,
active: active,
color: iconColor,
),
if (audio?.audioType == AudioType.podcast)
PlaybackRateButton(
active: active,
color: iconColor,
),
if (audio?.audioType == AudioType.podcast)
PlaybackRateButton(
active: active,
color: iconColor,
),
VolumeSliderPopup(color: iconColor),
IconButton(
tooltip: playerViewMode == PlayerViewMode.fullWindow
? context.l10n.leaveFullWindow
: context.l10n.fullWindow,
icon: Icon(
playerViewMode == PlayerViewMode.fullWindow
? Iconz().fullScreenExit
: Iconz().fullScreen,
color: iconColor,
),
onPressed: () {
appModel.setFullScreen(
playerViewMode == PlayerViewMode.fullWindow ? false : true,
);

appModel.setShowWindowControls(
(fullScreen == true && playerToTheRight) ? false : true,
);
},
VolumeSliderPopup(color: iconColor),
IconButton(
tooltip: playerViewMode == PlayerViewMode.fullWindow
? context.l10n.leaveFullWindow
: context.l10n.fullWindow,
icon: Icon(
playerViewMode == PlayerViewMode.fullWindow
? Iconz().fullWindowExit
: Iconz().fullWindow,
color: iconColor,
),
],
),
onPressed: () {
appModel.setFullScreen(
playerViewMode == PlayerViewMode.fullWindow ? false : true,
);

appModel.setShowWindowControls(
(fullScreen == true && playerToTheRight) ? false : true,
);
},
),
],
),
);
}
Expand Down
97 changes: 97 additions & 0 deletions lib/src/player/view/full_height_video_player.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import 'package:flutter/material.dart';
import 'package:media_kit_video/media_kit_video.dart';
import 'package:yaru/constants.dart';

import '../../../common.dart';
import '../../../get.dart';
import '../../../l10n.dart';
import '../player_model.dart';
import 'full_height_player_top_controls.dart';
import 'player_main_controls.dart';
import 'player_view.dart';

class FullHeightVideoPlayer extends StatelessWidget with WatchItMixin {
const FullHeightVideoPlayer({super.key, required this.playerViewMode});

final PlayerViewMode playerViewMode;

@override
Widget build(BuildContext context) {
const baseColor = Colors.white;

final audio = watchPropertyValue((PlayerModel m) => m.audio);
final playerModel = getIt<PlayerModel>();
final isOnline = watchPropertyValue((PlayerModel m) => m.isOnline);
final active = audio?.path != null || isOnline;

final controls = FullHeightPlayerTopControls(
iconColor: baseColor,
playerViewMode: playerViewMode,
padding: EdgeInsets.zero,
);

final mediaKitTheme = MaterialVideoControlsThemeData(
seekBarThumbColor: baseColor,
seekBarColor: baseColor.withOpacity(0.3),
seekBarPositionColor: baseColor.withOpacity(0.9),
seekBarBufferColor: baseColor.withOpacity(0.6),
buttonBarButtonColor: baseColor,
controlsHoverDuration: const Duration(seconds: 10),
primaryButtonBar: [
SizedBox(
width: 300,
child: PlayerMainControls(
active: active,
playPrevious: playerModel.playNext,
playNext: playerModel.playNext,
iconColor: baseColor,
avatarColor: baseColor.withOpacity(0.1),
),
),
],
seekBarMargin: const EdgeInsets.all(kYaruPagePadding),
topButtonBarMargin: const EdgeInsets.only(right: kYaruPagePadding),
topButtonBar: [
const Spacer(),
controls,
Tooltip(
message: context.l10n.leaveFullScreen,
child: MaterialFullscreenButton(
icon: Icon(
Iconz().fullScreenExit,
color: baseColor,
),
),
),
],
bottomButtonBar: [],
padding: const EdgeInsets.all(20),
);

return MaterialVideoControlsTheme(
key: ValueKey(audio?.url),
fullscreen: mediaKitTheme,
normal: mediaKitTheme.copyWith(
topButtonBar: [
const Spacer(),
controls,
Tooltip(
message: context.l10n.fullScreen,
child: MaterialFullscreenButton(
icon: Icon(
Iconz().fullScreen,
color: baseColor,
),
),
),
],
),
child: RepaintBoundary(
child: Video(
controller: getIt<PlayerModel>().controller,
controls: (state) => MaterialVideoControls(state),
),
),
);
}
}
Loading

0 comments on commit 8ce1402

Please sign in to comment.