diff --git a/backend/cmd/server/main.go b/backend/cmd/server/main.go index cbc8a62..fc92a2c 100644 --- a/backend/cmd/server/main.go +++ b/backend/cmd/server/main.go @@ -10,7 +10,7 @@ import ( func main() { monitor.Init(2000) - monitor.Infof("Starting Hightube Server v1.0.0-Beta4.1") + monitor.Infof("Starting Hightube Server v1.0.0-Beta4.7") // Initialize Database and run auto-migrations db.InitDB() diff --git a/frontend/lib/pages/player_page.dart b/frontend/lib/pages/player_page.dart index 20d7b3a..fbc7cd4 100644 --- a/frontend/lib/pages/player_page.dart +++ b/frontend/lib/pages/player_page.dart @@ -42,6 +42,7 @@ class _PlayerPageState extends State { bool _isRefreshing = false; bool _isFullscreen = false; bool _controlsVisible = true; + double _volume = kIsWeb ? 0.0 : 1.0; int _playerVersion = 0; String _selectedResolution = 'Source'; List _availableResolutions = const ['Source']; @@ -63,6 +64,7 @@ class _PlayerPageState extends State { _controller = VideoPlayerController.networkUrl(Uri.parse(playbackUrl)); try { await _controller!.initialize(); + await _controller!.setVolume(_volume); _controller!.play(); if (mounted) setState(() {}); } catch (e) { @@ -254,6 +256,76 @@ class _PlayerPageState extends State { _showControls(); } + Future _setVolume(double volume) async { + final nextVolume = volume.clamp(0.0, 1.0); + if (!mounted) { + return; + } + + setState(() => _volume = nextVolume); + + if (!kIsWeb && _controller != null) { + await _controller!.setVolume(nextVolume); + } + } + + Future _openVolumeSheet() async { + _showControls(); + await showModalBottomSheet( + context: context, + builder: (context) { + return SafeArea( + child: Padding( + padding: const EdgeInsets.fromLTRB(20, 16, 20, 28), + child: StatefulBuilder( + builder: (context, setSheetState) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Volume', + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: 12), + Row( + children: [ + Icon( + _volume == 0 + ? Icons.volume_off + : _volume < 0.5 + ? Icons.volume_down + : Icons.volume_up, + ), + Expanded( + child: Slider( + value: _volume, + min: 0, + max: 1, + divisions: 20, + label: '${(_volume * 100).round()}%', + onChanged: (value) { + setSheetState(() => _volume = value); + _setVolume(value); + }, + ), + ), + SizedBox( + width: 48, + child: Text('${(_volume * 100).round()}%'), + ), + ], + ), + ], + ); + }, + ), + ), + ); + }, + ); + } + void _showControls() { _controlsHideTimer?.cancel(); if (mounted) { @@ -439,6 +511,7 @@ class _PlayerPageState extends State { ? WebStreamPlayer( key: ValueKey('web-player-$_playerVersion'), streamUrl: _currentPlaybackUrl(), + volume: _volume, ) : _controller != null && _controller!.value.isInitialized ? AspectRatio( @@ -550,6 +623,15 @@ class _PlayerPageState extends State { label: "Refresh", onPressed: _refreshPlayer, ), + _buildControlButton( + icon: _volume == 0 + ? Icons.volume_off + : _volume < 0.5 + ? Icons.volume_down + : Icons.volume_up, + label: "Volume", + onPressed: _openVolumeSheet, + ), _buildControlButton( icon: _showDanmaku ? Icons.subtitles : Icons.subtitles_off, label: _showDanmaku ? "Danmaku On" : "Danmaku Off", diff --git a/frontend/lib/widgets/web_stream_player_stub.dart b/frontend/lib/widgets/web_stream_player_stub.dart index 09d8d6c..e826e77 100644 --- a/frontend/lib/widgets/web_stream_player_stub.dart +++ b/frontend/lib/widgets/web_stream_player_stub.dart @@ -2,11 +2,13 @@ import 'package:flutter/material.dart'; class WebStreamPlayer extends StatelessWidget { final String streamUrl; + final double volume; final int? refreshToken; const WebStreamPlayer({ super.key, required this.streamUrl, + required this.volume, this.refreshToken, }); diff --git a/frontend/lib/widgets/web_stream_player_web.dart b/frontend/lib/widgets/web_stream_player_web.dart index fade0c6..fe61b9e 100644 --- a/frontend/lib/widgets/web_stream_player_web.dart +++ b/frontend/lib/widgets/web_stream_player_web.dart @@ -1,3 +1,5 @@ +// ignore_for_file: avoid_web_libraries_in_flutter, deprecated_member_use + import 'dart:html' as html; import 'dart:ui_web' as ui_web; @@ -5,11 +7,13 @@ import 'package:flutter/material.dart'; class WebStreamPlayer extends StatefulWidget { final String streamUrl; + final double volume; final int? refreshToken; const WebStreamPlayer({ super.key, required this.streamUrl, + required this.volume, this.refreshToken, }); @@ -19,6 +23,7 @@ class WebStreamPlayer extends StatefulWidget { class _WebStreamPlayerState extends State { late final String _viewType; + html.IFrameElement? _iframe; @override void initState() { @@ -29,15 +34,30 @@ class _WebStreamPlayerState extends State { ui_web.platformViewRegistry.registerViewFactory(_viewType, (int viewId) { final iframe = html.IFrameElement() ..src = - 'flv_player.html?v=$cacheBuster&src=${Uri.encodeComponent(widget.streamUrl)}' + 'flv_player.html?v=$cacheBuster' + '&src=${Uri.encodeComponent(widget.streamUrl)}' + '&volume=${widget.volume}' ..style.border = '0' ..style.width = '100%' ..style.height = '100%' + ..style.pointerEvents = 'none' ..allow = 'autoplay; fullscreen'; + _iframe = iframe; return iframe; }); } + @override + void didUpdateWidget(covariant WebStreamPlayer oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.volume != widget.volume) { + _iframe?.contentWindow?.postMessage({ + 'type': 'setVolume', + 'value': widget.volume, + }, '*'); + } + } + @override Widget build(BuildContext context) { return HtmlElementView(viewType: _viewType); diff --git a/frontend/pubspec.yaml b/frontend/pubspec.yaml index 1ad5034..fe41163 100644 --- a/frontend/pubspec.yaml +++ b/frontend/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.0.0-beta4.1 +version: 1.0.0-beta4.7 environment: sdk: ^3.11.1 diff --git a/frontend/web/flv_player.html b/frontend/web/flv_player.html index a7c8a14..9f31335 100644 --- a/frontend/web/flv_player.html +++ b/frontend/web/flv_player.html @@ -45,14 +45,21 @@ - +
Loading live stream...