Unify web player controls and add volume control
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -42,6 +42,7 @@ class _PlayerPageState extends State<PlayerPage> {
|
||||
bool _isRefreshing = false;
|
||||
bool _isFullscreen = false;
|
||||
bool _controlsVisible = true;
|
||||
double _volume = kIsWeb ? 0.0 : 1.0;
|
||||
int _playerVersion = 0;
|
||||
String _selectedResolution = 'Source';
|
||||
List<String> _availableResolutions = const ['Source'];
|
||||
@@ -63,6 +64,7 @@ class _PlayerPageState extends State<PlayerPage> {
|
||||
_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<PlayerPage> {
|
||||
_showControls();
|
||||
}
|
||||
|
||||
Future<void> _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<void> _openVolumeSheet() async {
|
||||
_showControls();
|
||||
await showModalBottomSheet<void>(
|
||||
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<PlayerPage> {
|
||||
? WebStreamPlayer(
|
||||
key: ValueKey('web-player-$_playerVersion'),
|
||||
streamUrl: _currentPlaybackUrl(),
|
||||
volume: _volume,
|
||||
)
|
||||
: _controller != null && _controller!.value.isInitialized
|
||||
? AspectRatio(
|
||||
@@ -550,6 +623,15 @@ class _PlayerPageState extends State<PlayerPage> {
|
||||
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",
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
|
||||
@@ -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<WebStreamPlayer> {
|
||||
late final String _viewType;
|
||||
html.IFrameElement? _iframe;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -29,15 +34,30 @@ class _WebStreamPlayerState extends State<WebStreamPlayer> {
|
||||
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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -45,14 +45,21 @@
|
||||
<script src="flv.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<video id="player" controls autoplay muted playsinline></video>
|
||||
<video id="player" autoplay muted playsinline></video>
|
||||
<div id="message">Loading live stream...</div>
|
||||
<script>
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const streamUrl = params.get('src');
|
||||
const initialVolume = Number.parseFloat(params.get('volume') || '1');
|
||||
const video = document.getElementById('player');
|
||||
const message = document.getElementById('message');
|
||||
|
||||
function applyVolume(value) {
|
||||
const normalized = Number.isFinite(value) ? Math.max(0, Math.min(1, value)) : 1;
|
||||
video.volume = normalized;
|
||||
video.muted = normalized === 0;
|
||||
}
|
||||
|
||||
function showMessage(text) {
|
||||
video.style.display = 'none';
|
||||
message.style.display = 'flex';
|
||||
@@ -66,6 +73,8 @@
|
||||
} else if (!flvjs.isSupported()) {
|
||||
showMessage('This browser does not support FLV playback.');
|
||||
} else {
|
||||
applyVolume(initialVolume);
|
||||
|
||||
const player = flvjs.createPlayer({
|
||||
type: 'flv',
|
||||
url: streamUrl,
|
||||
@@ -90,6 +99,13 @@
|
||||
video.style.display = 'block';
|
||||
message.style.display = 'none';
|
||||
|
||||
window.addEventListener('message', function(event) {
|
||||
const data = event.data || {};
|
||||
if (data.type === 'setVolume') {
|
||||
applyVolume(Number(data.value));
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('beforeunload', function() {
|
||||
player.destroy();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user