Initial commit

This commit is contained in:
2024-12-15 00:52:28 +01:00
commit 540b504c2e
33 changed files with 2217 additions and 0 deletions

153
lib/api/commands.dart Normal file
View File

@@ -0,0 +1,153 @@
import 'dart:convert';
sealed class ConnectionEvent {}
class Connect extends ConnectionEvent {
final String uri;
Connect(this.uri);
}
class Disconnect extends ConnectionEvent {}
class Command extends ConnectionEvent {
final String type;
final Map<String, dynamic> value;
Command({
required this.type,
required this.value,
});
factory Command.fromJson(Map<String, dynamic> json) {
return Command(
type: json['type'],
value: json['value'],
);
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> result = Map.from(value);
result['type'] = type;
return result;
}
String toJsonString() {
return jsonEncode(toJson());
}
factory Command.subscribe(String property) {
return Command(
type: 'subscribe',
value: {
'property': property,
},
);
}
factory Command.unsubscribeAll() {
return Command(
type: 'unsubscribe_all',
value: {},
);
}
factory Command.load(String url) {
return Command(
type: 'load',
value: {
'url': url,
},
);
}
factory Command.togglePlayback() {
return Command(
type: 'toggle_playback',
value: {},
);
}
factory Command.volume(double volume) {
return Command(
type: 'volume',
value: {
'volume': volume,
},
);
}
factory Command.time(double time) {
return Command(
type: 'time',
value: {
'time': time,
},
);
}
factory Command.playlistNext() {
return Command(
type: 'playlist_next',
value: {},
);
}
factory Command.playlistPrevious() {
return Command(
type: 'playlist_previous',
value: {},
);
}
factory Command.playlistGoto(int position) {
return Command(
type: 'playlist_goto',
value: {
'position': position,
},
);
}
factory Command.playlistClear() {
return Command(
type: 'playlist_clear',
value: {},
);
}
factory Command.playlistRemove(int position) {
return Command(
type: 'playlist_remove',
value: {
'position': position,
},
);
}
factory Command.playlistMove(int from, int to) {
return Command(
type: 'playlist_move',
value: {
'from': from,
'to': to,
},
);
}
factory Command.shuffle() {
return Command(
type: 'shuffle',
value: {},
);
}
factory Command.setLooping(bool value) {
return Command(
type: 'set_looping',
value: {
'value': value,
},
);
}
}

206
lib/api/events.dart Normal file
View File

@@ -0,0 +1,206 @@
// TODO: handle typing and deserialization of events
import 'package:flutter/foundation.dart';
import '../state/player_state.dart';
// NOTE: see DEFAULT_PROPERTY_SUBSCRIPTIONS in the websocket API source for greg-ng.
@immutable
sealed class Event {
const Event();
}
@immutable
class ClearPlayerState extends Event {
const ClearPlayerState() : super();
}
@immutable
class InitialPlayerState extends Event {
final PlayerState playerState;
const InitialPlayerState(this.playerState) : super();
factory InitialPlayerState.fromJson(dynamic json) {
return InitialPlayerState(PlayerState.fromJson(json));
}
}
@immutable
sealed class PropertyChangedEvent extends Event {
final bool local;
const PropertyChangedEvent({this.local = false}) : super();
}
@immutable
class PlaylistChange extends PropertyChangedEvent {
final Playlist playlist;
const PlaylistChange(this.playlist, { super.local }) : super();
factory PlaylistChange.fromJson(dynamic json) {
return PlaylistChange(
(json as List).map((e) => PlaylistItem.fromJson(e)).toList(),
);
}
}
@immutable
class LoopPlaylistChange extends PropertyChangedEvent {
final bool isLooping;
const LoopPlaylistChange(this.isLooping, { super.local }) : super();
factory LoopPlaylistChange.fromJson(dynamic json) {
return LoopPlaylistChange(json == "inf");
}
}
@immutable
class PercentPositionChange extends PropertyChangedEvent {
final double currentPercentPosition;
const PercentPositionChange(this.currentPercentPosition, { super.local }) : super();
factory PercentPositionChange.fromJson(dynamic json) {
return PercentPositionChange(json ?? 0.0);
}
}
@immutable
class VolumeChange extends PropertyChangedEvent {
final double volume;
const VolumeChange(this.volume, { super.local }) : super();
factory VolumeChange.fromJson(dynamic json) {
return VolumeChange(json);
}
}
@immutable
class DurationChange extends PropertyChangedEvent {
final Duration duration;
const DurationChange(this.duration, { super.local }) : super();
factory DurationChange.fromJson(dynamic json) {
return DurationChange(Duration(milliseconds: ((json ?? 0.0) * 1000).round()));
}
}
@immutable
class PauseChange extends PropertyChangedEvent {
final bool isPaused;
const PauseChange(this.isPaused, { super.local }) : super();
factory PauseChange.fromJson(dynamic json) {
return PauseChange(json as bool);
}
}
@immutable
class MuteChange extends PropertyChangedEvent {
final bool isMuted;
const MuteChange(this.isMuted, { super.local }) : super();
factory MuteChange.fromJson(dynamic json) {
return MuteChange(json as bool);
}
}
@immutable
class TrackListChange extends PropertyChangedEvent {
final List<SubtitleTrack> tracks;
const TrackListChange(this.tracks, { super.local }) : super();
factory TrackListChange.fromJson(dynamic json) {
final trackList = json as List;
trackList.retainWhere((e) => e is Map && e['type'] == 'sub');
return TrackListChange(
trackList.map((e) => SubtitleTrack.fromJson(e)).toList(),
);
}
}
@immutable
class DemuxerCacheStateChange extends PropertyChangedEvent {
final double cachedTimestamp;
const DemuxerCacheStateChange(this.cachedTimestamp, { super.local }) : super();
factory DemuxerCacheStateChange.fromJson(dynamic json) {
final demuxerCacheState = json as Map?;
final cachedTimestamp =
demuxerCacheState != null ? demuxerCacheState['cache-end'] ?? 0.0 : 0.0;
return DemuxerCacheStateChange(cachedTimestamp);
}
}
@immutable
class PausedForCacheChange extends PropertyChangedEvent {
final bool isPausedForCache;
const PausedForCacheChange(this.isPausedForCache, { super.local }) : super();
factory PausedForCacheChange.fromJson(dynamic json) {
return PausedForCacheChange(json as bool? ?? false);
}
}
// @immutable
// class ChapterListChange extends PropertyChangedEvent {
// final List<Chapter> chapters;
// ChapterListChange(this.chapters);
// factory ChapterListChange.fromJson(dynamic json) {
// return ChapterListChange(
// (json as List).map((e) => Chapter.fromJson(e)).toList(),
// );
// }
// }
Event? parseEvent(dynamic value) {
if (value is String) {
return null;
}
if (value is Map && value.containsKey('property-change')) {
final propertyChange = value['property-change'];
switch (propertyChange['name']) {
case 'playlist':
return PlaylistChange.fromJson(propertyChange['data']);
case 'loop-playlist':
return LoopPlaylistChange.fromJson(propertyChange['data']);
case 'percent-pos':
return PercentPositionChange.fromJson(propertyChange['data']);
case 'volume':
return VolumeChange.fromJson(propertyChange['data']);
case 'duration':
return DurationChange.fromJson(propertyChange['data']);
case 'pause':
return PauseChange.fromJson(propertyChange['data']);
case 'mute':
return MuteChange.fromJson(propertyChange['data']);
case 'track-list':
return TrackListChange.fromJson(propertyChange['data']);
case 'demuxer-cache-state':
return DemuxerCacheStateChange.fromJson(propertyChange['data']);
case 'paused-for-cache':
return PausedForCacheChange.fromJson(propertyChange['data']);
// "chapter-list",
// "paused-for-cache",
default:
return null;
}
}
return null;
}