mirror of
https://github.com/nostrlabs-io/zap-stream-flutter.git
synced 2025-06-15 11:48:21 +00:00
feat: background playback (wip)
https://github.com/ryanheise/audio_service/issues/1124
This commit is contained in:
@ -25,7 +25,11 @@ void runZapStream() {
|
||||
supportedLocales: AppLocaleUtils.supportedLocales,
|
||||
localizationsDelegates: GlobalMaterialLocalizations.delegates,
|
||||
theme: ThemeData.localize(
|
||||
ThemeData(colorScheme: ColorScheme.dark(), highlightColor: PRIMARY_1),
|
||||
ThemeData(
|
||||
colorScheme: ColorScheme.dark(),
|
||||
highlightColor: PRIMARY_1,
|
||||
useMaterial3: true,
|
||||
),
|
||||
TextTheme(),
|
||||
),
|
||||
routerConfig: GoRouter(
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'dart:developer' as developer;
|
||||
|
||||
import 'package:audio_service/audio_service.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
@ -7,6 +8,9 @@ import 'package:zap_stream_flutter/app.dart';
|
||||
import 'package:zap_stream_flutter/const.dart';
|
||||
import 'package:zap_stream_flutter/i18n/strings.g.dart';
|
||||
import 'package:zap_stream_flutter/notifications.dart';
|
||||
import 'package:zap_stream_flutter/player.dart';
|
||||
|
||||
late final MainPlayer mainPlayer;
|
||||
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
@ -20,5 +24,14 @@ Future<void> main() async {
|
||||
developer.log("Failed to setup notifications: $e");
|
||||
});
|
||||
|
||||
mainPlayer = await AudioService.init(
|
||||
builder: () => MainPlayer(),
|
||||
config: AudioServiceConfig(
|
||||
androidNotificationChannelId: "io.nostrlabs.zap_stream_flutter.player",
|
||||
androidNotificationChannelName: "player",
|
||||
androidNotificationOngoing: true
|
||||
),
|
||||
);
|
||||
|
||||
runZapStream();
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ import 'package:zap_stream_flutter/widgets/notifications_button.dart';
|
||||
import 'package:zap_stream_flutter/widgets/pill.dart';
|
||||
import 'package:zap_stream_flutter/widgets/profile.dart';
|
||||
import 'package:zap_stream_flutter/widgets/stream_info.dart';
|
||||
import 'package:zap_stream_flutter/widgets/video_player.dart';
|
||||
import 'package:zap_stream_flutter/widgets/video_player_main.dart';
|
||||
import 'package:zap_stream_flutter/widgets/zap.dart';
|
||||
|
||||
class StreamPage extends StatefulWidget {
|
||||
@ -141,11 +141,13 @@ class _StreamPage extends State<StreamPage> with RouteAware {
|
||||
aspectRatio: 16 / 9,
|
||||
child:
|
||||
(stream.info.stream != null && !_offScreen)
|
||||
? VideoPlayerWidget(
|
||||
? MainVideoPlayerWidget(
|
||||
url: stream.info.stream!,
|
||||
placeholder: stream.info.image,
|
||||
aspectRatio: 16 / 9,
|
||||
isLive: true,
|
||||
title: stream.info.title,
|
||||
|
||||
)
|
||||
: (stream.info.image?.isNotEmpty ?? false)
|
||||
? ProxyImg(url: stream.info.image)
|
||||
|
100
lib/player.dart
Normal file
100
lib/player.dart
Normal file
@ -0,0 +1,100 @@
|
||||
import 'package:audio_service/audio_service.dart';
|
||||
import 'package:chewie/chewie.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
import 'package:zap_stream_flutter/const.dart';
|
||||
import 'package:zap_stream_flutter/imgproxy.dart';
|
||||
|
||||
class MainPlayer extends BaseAudioHandler {
|
||||
VideoPlayerController? _controller;
|
||||
ChewieController? _chewieController;
|
||||
|
||||
ChewieController? get chewie {
|
||||
return _chewieController;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> play() async {
|
||||
await _chewieController?.play();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> pause() async {
|
||||
await _chewieController?.pause();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> stop() async {
|
||||
await _chewieController?.pause();
|
||||
}
|
||||
|
||||
void loadUrl(
|
||||
String url, {
|
||||
String? title,
|
||||
bool? autoPlay,
|
||||
double? aspectRatio,
|
||||
bool? isLive,
|
||||
String? placeholder,
|
||||
String? artist,
|
||||
}) {
|
||||
if (_chewieController != null) {
|
||||
_chewieController!.dispose();
|
||||
_controller!.dispose();
|
||||
}
|
||||
|
||||
_controller = VideoPlayerController.networkUrl(
|
||||
Uri.parse(url),
|
||||
httpHeaders: Map.from({"user-agent": userAgent}),
|
||||
);
|
||||
_chewieController = ChewieController(
|
||||
videoPlayerController: _controller!,
|
||||
autoPlay: autoPlay ?? true,
|
||||
aspectRatio: aspectRatio,
|
||||
isLive: isLive ?? false,
|
||||
autoInitialize: true,
|
||||
allowedScreenSleep: false,
|
||||
placeholder:
|
||||
(placeholder?.isNotEmpty ?? false)
|
||||
? ProxyImg(url: placeholder!)
|
||||
: null,
|
||||
);
|
||||
|
||||
// insert media item
|
||||
mediaItem.add(
|
||||
MediaItem(
|
||||
id: url.hashCode.toString(),
|
||||
title: title ?? url,
|
||||
artist: artist,
|
||||
isLive: _chewieController!.isLive,
|
||||
artUri:
|
||||
(placeholder?.isNotEmpty ?? false) ? Uri.parse(placeholder!) : null,
|
||||
),
|
||||
);
|
||||
_chewieController!.videoPlayerController.addListener(updatePlayerState);
|
||||
}
|
||||
|
||||
void updatePlayerState() {
|
||||
final isPlaying =
|
||||
_chewieController?.videoPlayerController.value.isPlaying ?? false;
|
||||
|
||||
playbackState.add(
|
||||
playbackState.value.copyWith(
|
||||
controls: [
|
||||
if (playbackState.value.playing)
|
||||
MediaControl.pause
|
||||
else
|
||||
MediaControl.play,
|
||||
MediaControl.stop,
|
||||
],
|
||||
playing: isPlaying,
|
||||
processingState: switch (_chewieController
|
||||
?.videoPlayerController
|
||||
.value
|
||||
.isInitialized) {
|
||||
true => AudioProcessingState.ready,
|
||||
false => AudioProcessingState.idle,
|
||||
_ => AudioProcessingState.completed,
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
53
lib/widgets/video_player_main.dart
Normal file
53
lib/widgets/video_player_main.dart
Normal file
@ -0,0 +1,53 @@
|
||||
import 'package:chewie/chewie.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:zap_stream_flutter/main.dart';
|
||||
|
||||
class MainVideoPlayerWidget extends StatefulWidget {
|
||||
final String url;
|
||||
final String? title;
|
||||
final String? placeholder;
|
||||
final double? aspectRatio;
|
||||
final bool? autoPlay;
|
||||
final bool? isLive;
|
||||
|
||||
const MainVideoPlayerWidget({
|
||||
super.key,
|
||||
required this.url,
|
||||
this.title,
|
||||
this.placeholder,
|
||||
this.aspectRatio,
|
||||
this.autoPlay,
|
||||
this.isLive,
|
||||
});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _MainVideoPlayerWidget();
|
||||
}
|
||||
|
||||
class _MainVideoPlayerWidget extends State<MainVideoPlayerWidget> {
|
||||
@override
|
||||
void initState() {
|
||||
mainPlayer.loadUrl(
|
||||
widget.url,
|
||||
title: widget.title,
|
||||
placeholder: widget.placeholder,
|
||||
aspectRatio: widget.aspectRatio,
|
||||
autoPlay: widget.autoPlay,
|
||||
isLive: widget.isLive,
|
||||
artist: "zap.stream"
|
||||
);
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
mainPlayer.stop();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Chewie(controller: mainPlayer.chewie!);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user