✨ Playback server
This commit is contained in:
39
lib/services/server/active_sourced_track.dart
Executable file
39
lib/services/server/active_sourced_track.dart
Executable file
@ -0,0 +1,39 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:rhythm_box/providers/audio_player.dart';
|
||||
import 'package:rhythm_box/services/audio_player/audio_player.dart';
|
||||
import 'package:rhythm_box/services/sourced_track/models/source_info.dart';
|
||||
import 'package:rhythm_box/services/sourced_track/sourced_track.dart';
|
||||
|
||||
class ActiveSourcedTrackProvider extends GetxController {
|
||||
Rx<SourcedTrack?> state = Rx(null);
|
||||
|
||||
void updateTrack(SourcedTrack? sourcedTrack) {
|
||||
state.value = sourcedTrack;
|
||||
}
|
||||
|
||||
Future<void> populateSibling() async {
|
||||
if (state.value == null) return;
|
||||
state.value = await state.value!.copyWithSibling();
|
||||
}
|
||||
|
||||
Future<void> swapSibling(SourceInfo sibling) async {
|
||||
if (state.value == null) return;
|
||||
await populateSibling();
|
||||
final newTrack = await state.value!.swapWithSibling(sibling);
|
||||
if (newTrack == null) return;
|
||||
|
||||
state.value = newTrack;
|
||||
await audioPlayer.pause();
|
||||
|
||||
final playback = Get.find<AudioPlayerProvider>();
|
||||
final oldActiveIndex = audioPlayer.currentIndex;
|
||||
|
||||
await playback.addTracksAtFirst([newTrack]);
|
||||
await Future.delayed(const Duration(milliseconds: 50));
|
||||
await playback.jumpToTrack(newTrack);
|
||||
|
||||
await audioPlayer.removeTrack(oldActiveIndex);
|
||||
|
||||
await audioPlayer.resume();
|
||||
}
|
||||
}
|
66
lib/services/server/routes/playback.dart
Executable file
66
lib/services/server/routes/playback.dart
Executable file
@ -0,0 +1,66 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:dio/dio.dart' hide Response;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:get/get.dart' hide Response;
|
||||
import 'package:rhythm_box/providers/audio_player.dart';
|
||||
import 'package:rhythm_box/services/audio_player/audio_player.dart';
|
||||
import 'package:rhythm_box/services/server/active_sourced_track.dart';
|
||||
import 'package:rhythm_box/services/server/sourced_track.dart';
|
||||
import 'package:rhythm_box/services/sourced_track/sourced_track.dart';
|
||||
import 'package:shelf/shelf.dart';
|
||||
|
||||
class ServerPlaybackRoutesProvider {
|
||||
/// @get('/stream/<trackId>')
|
||||
Future<Response> getStreamTrackId(Request request, String trackId) async {
|
||||
final AudioPlayerProvider playback = Get.find();
|
||||
|
||||
try {
|
||||
final track = playback.state.value.tracks
|
||||
.firstWhere((element) => element.id == trackId);
|
||||
|
||||
final ActiveSourcedTrackProvider activeSourcedTrack = Get.find();
|
||||
final sourcedTrack = activeSourcedTrack.state.value?.id == track.id
|
||||
? activeSourcedTrack
|
||||
: await Get.find<SourcedTrackProvider>().fetch(RhythmMedia(track));
|
||||
|
||||
activeSourcedTrack.updateTrack(sourcedTrack as SourcedTrack?);
|
||||
|
||||
final res = await Dio().get(
|
||||
sourcedTrack!.url,
|
||||
options: Options(
|
||||
headers: {
|
||||
...request.headers,
|
||||
"User-Agent":
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
|
||||
"host": Uri.parse(sourcedTrack.url).host,
|
||||
"Cache-Control": "max-age=0",
|
||||
"Connection": "keep-alive",
|
||||
},
|
||||
responseType: ResponseType.stream,
|
||||
validateStatus: (status) => status! < 500,
|
||||
),
|
||||
);
|
||||
|
||||
final audioStream =
|
||||
(res.data?.stream as Stream<Uint8List>?)?.asBroadcastStream();
|
||||
|
||||
audioStream!.listen(
|
||||
(event) {},
|
||||
cancelOnError: true,
|
||||
);
|
||||
|
||||
return Response(
|
||||
res.statusCode!,
|
||||
body: audioStream,
|
||||
context: {
|
||||
"shelf.io.buffer_output": false,
|
||||
},
|
||||
headers: res.headers.map,
|
||||
);
|
||||
} catch (e) {
|
||||
log('[PlaybackSever] Error: $e');
|
||||
return Response.internalServerError();
|
||||
}
|
||||
}
|
||||
}
|
48
lib/services/server/server.dart
Executable file
48
lib/services/server/server.dart
Executable file
@ -0,0 +1,48 @@
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
import 'dart:math' hide log;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:get/get.dart' hide Response;
|
||||
import 'package:rhythm_box/services/rhythm_media.dart';
|
||||
import 'package:rhythm_box/services/server/routes/playback.dart';
|
||||
import 'package:shelf/shelf.dart';
|
||||
import 'package:shelf/shelf_io.dart';
|
||||
import 'package:shelf_router/shelf_router.dart';
|
||||
|
||||
class PlaybackServerProvider extends GetxController {
|
||||
HttpServer? _server;
|
||||
Router? _router;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
_initServer();
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
Future<void> _initServer() async {
|
||||
const pipeline = Pipeline();
|
||||
if (kDebugMode) {
|
||||
pipeline.addMiddleware(logRequests());
|
||||
}
|
||||
|
||||
final port = Random().nextInt(17500) + 5000;
|
||||
|
||||
RhythmMedia.serverPort = port;
|
||||
|
||||
_router = Router();
|
||||
_router!.get("/ping", (Request request) => Response.ok("pong"));
|
||||
_router!.get(
|
||||
"/stream/<trackId>",
|
||||
Get.find<ServerPlaybackRoutesProvider>().getStreamTrackId,
|
||||
);
|
||||
|
||||
_server = await serve(
|
||||
pipeline.addHandler(_router!.call),
|
||||
InternetAddress.anyIPv4,
|
||||
port,
|
||||
);
|
||||
|
||||
log('[Playback] Playback server at http://${_server!.address.host}:${_server!.port}');
|
||||
}
|
||||
}
|
17
lib/services/server/sourced_track.dart
Executable file
17
lib/services/server/sourced_track.dart
Executable file
@ -0,0 +1,17 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:rhythm_box/services/audio_player/audio_player.dart';
|
||||
import 'package:rhythm_box/services/local_track.dart';
|
||||
import 'package:rhythm_box/services/sourced_track/sourced_track.dart';
|
||||
|
||||
class SourcedTrackProvider extends GetxController {
|
||||
Future<SourcedTrack?> fetch(RhythmMedia? media) async {
|
||||
final track = media?.track;
|
||||
if (track == null || track is LocalTrack) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final sourcedTrack = await SourcedTrack.fetchFromTrack(track: track);
|
||||
|
||||
return sourcedTrack;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user