0.6.5 - Local streaming http server
This commit is contained in:
parent
28c2de55fb
commit
21e7f55017
31 changed files with 1744 additions and 460 deletions
|
@ -29,19 +29,9 @@ class Cache {
|
|||
@JsonKey(defaultValue: [])
|
||||
List<Track> history = [];
|
||||
|
||||
//Cache playlist sort type {id: sort}
|
||||
@JsonKey(defaultValue: {})
|
||||
Map<String, SortType> playlistSort;
|
||||
|
||||
//Sort
|
||||
@JsonKey(defaultValue: AlbumSortType.DEFAULT)
|
||||
AlbumSortType albumSort;
|
||||
@JsonKey(defaultValue: ArtistSortType.DEFAULT)
|
||||
ArtistSortType artistSort;
|
||||
@JsonKey(defaultValue: PlaylistSortType.DEFAULT)
|
||||
PlaylistSortType libraryPlaylistSort;
|
||||
@JsonKey(defaultValue: SortType.DEFAULT)
|
||||
SortType trackSort;
|
||||
//All sorting cached
|
||||
@JsonKey(defaultValue: [])
|
||||
List<Sorting> sorts = [];
|
||||
|
||||
//Sleep timer
|
||||
@JsonKey(ignore: true)
|
||||
|
|
|
@ -16,21 +16,11 @@ Cache _$CacheFromJson(Map<String, dynamic> json) {
|
|||
e == null ? null : Track.fromJson(e as Map<String, dynamic>))
|
||||
?.toList() ??
|
||||
[]
|
||||
..playlistSort = (json['playlistSort'] as Map<String, dynamic>)?.map(
|
||||
(k, e) => MapEntry(k, _$enumDecodeNullable(_$SortTypeEnumMap, e)),
|
||||
) ??
|
||||
{}
|
||||
..albumSort =
|
||||
_$enumDecodeNullable(_$AlbumSortTypeEnumMap, json['albumSort']) ??
|
||||
AlbumSortType.DEFAULT
|
||||
..artistSort =
|
||||
_$enumDecodeNullable(_$ArtistSortTypeEnumMap, json['artistSort']) ??
|
||||
ArtistSortType.DEFAULT
|
||||
..libraryPlaylistSort = _$enumDecodeNullable(
|
||||
_$PlaylistSortTypeEnumMap, json['libraryPlaylistSort']) ??
|
||||
PlaylistSortType.DEFAULT
|
||||
..trackSort = _$enumDecodeNullable(_$SortTypeEnumMap, json['trackSort']) ??
|
||||
SortType.DEFAULT
|
||||
..sorts = (json['sorts'] as List)
|
||||
?.map((e) =>
|
||||
e == null ? null : Sorting.fromJson(e as Map<String, dynamic>))
|
||||
?.toList() ??
|
||||
[]
|
||||
..searchHistory =
|
||||
Cache._searchHistoryFromJson(json['searchHistory2'] as List)
|
||||
..threadsWarning = json['threadsWarning'] as bool ?? false
|
||||
|
@ -40,18 +30,25 @@ Cache _$CacheFromJson(Map<String, dynamic> json) {
|
|||
Map<String, dynamic> _$CacheToJson(Cache instance) => <String, dynamic>{
|
||||
'libraryTracks': instance.libraryTracks,
|
||||
'history': instance.history,
|
||||
'playlistSort': instance.playlistSort
|
||||
?.map((k, e) => MapEntry(k, _$SortTypeEnumMap[e])),
|
||||
'albumSort': _$AlbumSortTypeEnumMap[instance.albumSort],
|
||||
'artistSort': _$ArtistSortTypeEnumMap[instance.artistSort],
|
||||
'libraryPlaylistSort':
|
||||
_$PlaylistSortTypeEnumMap[instance.libraryPlaylistSort],
|
||||
'trackSort': _$SortTypeEnumMap[instance.trackSort],
|
||||
'sorts': instance.sorts,
|
||||
'searchHistory2': Cache._searchHistoryToJson(instance.searchHistory),
|
||||
'threadsWarning': instance.threadsWarning,
|
||||
'lastUpdateCheck': instance.lastUpdateCheck,
|
||||
};
|
||||
|
||||
SearchHistoryItem _$SearchHistoryItemFromJson(Map<String, dynamic> json) {
|
||||
return SearchHistoryItem(
|
||||
json['data'],
|
||||
_$enumDecodeNullable(_$SearchHistoryItemTypeEnumMap, json['type']),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$SearchHistoryItemToJson(SearchHistoryItem instance) =>
|
||||
<String, dynamic>{
|
||||
'data': instance.data,
|
||||
'type': _$SearchHistoryItemTypeEnumMap[instance.type],
|
||||
};
|
||||
|
||||
T _$enumDecode<T>(
|
||||
Map<T, dynamic> enumValues,
|
||||
dynamic source, {
|
||||
|
@ -84,49 +81,6 @@ T _$enumDecodeNullable<T>(
|
|||
return _$enumDecode<T>(enumValues, source, unknownValue: unknownValue);
|
||||
}
|
||||
|
||||
const _$SortTypeEnumMap = {
|
||||
SortType.DEFAULT: 'DEFAULT',
|
||||
SortType.REVERSE: 'REVERSE',
|
||||
SortType.ALPHABETIC: 'ALPHABETIC',
|
||||
SortType.ARTIST: 'ARTIST',
|
||||
};
|
||||
|
||||
const _$AlbumSortTypeEnumMap = {
|
||||
AlbumSortType.DEFAULT: 'DEFAULT',
|
||||
AlbumSortType.REVERSE: 'REVERSE',
|
||||
AlbumSortType.ALPHABETIC: 'ALPHABETIC',
|
||||
AlbumSortType.ARTIST: 'ARTIST',
|
||||
AlbumSortType.DATE: 'DATE',
|
||||
};
|
||||
|
||||
const _$ArtistSortTypeEnumMap = {
|
||||
ArtistSortType.DEFAULT: 'DEFAULT',
|
||||
ArtistSortType.REVERSE: 'REVERSE',
|
||||
ArtistSortType.POPULARITY: 'POPULARITY',
|
||||
ArtistSortType.ALPHABETIC: 'ALPHABETIC',
|
||||
};
|
||||
|
||||
const _$PlaylistSortTypeEnumMap = {
|
||||
PlaylistSortType.DEFAULT: 'DEFAULT',
|
||||
PlaylistSortType.REVERSE: 'REVERSE',
|
||||
PlaylistSortType.ALPHABETIC: 'ALPHABETIC',
|
||||
PlaylistSortType.USER: 'USER',
|
||||
PlaylistSortType.TRACK_COUNT: 'TRACK_COUNT',
|
||||
};
|
||||
|
||||
SearchHistoryItem _$SearchHistoryItemFromJson(Map<String, dynamic> json) {
|
||||
return SearchHistoryItem(
|
||||
json['data'],
|
||||
_$enumDecodeNullable(_$SearchHistoryItemTypeEnumMap, json['type']),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$SearchHistoryItemToJson(SearchHistoryItem instance) =>
|
||||
<String, dynamic>{
|
||||
'data': instance.data,
|
||||
'type': _$SearchHistoryItemTypeEnumMap[instance.type],
|
||||
};
|
||||
|
||||
const _$SearchHistoryItemTypeEnumMap = {
|
||||
SearchHistoryItemType.TRACK: 'TRACK',
|
||||
SearchHistoryItemType.ALBUM: 'ALBUM',
|
||||
|
|
|
@ -459,12 +459,24 @@ class DeezerAPI {
|
|||
return data['results']['data'].map<Track>((t) => Track.fromPrivateJson(t)).toList();
|
||||
}
|
||||
|
||||
// Get similar tracks for track with id [trackId]
|
||||
//Get similar tracks for track with id [trackId]
|
||||
Future<List<Track>> playMix(String trackId) async {
|
||||
Map data = await callApi('song.getContextualTrackMix', params: {
|
||||
'sng_ids': [trackId]
|
||||
});
|
||||
return data['results']['data'].map<Track>((t) => Track.fromPrivateJson(t)).toList();
|
||||
}
|
||||
|
||||
Future<List<ShowEpisode>> allShowEpisodes(String showId) async {
|
||||
Map data = await callApi('deezer.pageShow', params: {
|
||||
'country': settings.deezerCountry,
|
||||
'lang': settings.deezerLanguage,
|
||||
'nb': 1000,
|
||||
'show_id': showId,
|
||||
'start': 0,
|
||||
'user_id': int.parse(deezerAPI.userId)
|
||||
});
|
||||
return data['results']['EPISODES']['data'].map<ShowEpisode>((e) => ShowEpisode.fromPrivateJson(e)).toList();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:io';
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:audio_service/audio_service.dart';
|
||||
import 'package:freezer/api/cache.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
@ -33,12 +34,13 @@ class Track {
|
|||
bool favorite;
|
||||
int diskNumber;
|
||||
bool explicit;
|
||||
int favoriteDate;
|
||||
//Date added to playlist / favorites
|
||||
int addedDate;
|
||||
|
||||
List<dynamic> playbackDetails;
|
||||
|
||||
Track({this.id, this.title, this.duration, this.album, this.playbackDetails, this.albumArt,
|
||||
this.artists, this.trackNumber, this.offline, this.lyrics, this.favorite, this.diskNumber, this.explicit, this.favoriteDate});
|
||||
this.artists, this.trackNumber, this.offline, this.lyrics, this.favorite, this.diskNumber, this.explicit, this.addedDate});
|
||||
|
||||
String get artistString => artists.map<String>((art) => art.name).join(', ');
|
||||
String get durationString => "${duration.inMinutes}:${duration.inSeconds.remainder(60).toString().padLeft(2, '0')}";
|
||||
|
@ -141,7 +143,7 @@ class Track {
|
|||
favorite: favorite,
|
||||
diskNumber: int.parse(json['DISK_NUMBER']??'1'),
|
||||
explicit: (json['EXPLICIT_LYRICS'].toString() == '1') ? true:false,
|
||||
favoriteDate: json['DATE_ADD']
|
||||
addedDate: json['DATE_ADD']
|
||||
);
|
||||
}
|
||||
Map<String, dynamic> toSQL({off = false}) => {
|
||||
|
@ -157,7 +159,7 @@ class Track {
|
|||
'favorite': (favorite??0)?1:0,
|
||||
'diskNumber': diskNumber,
|
||||
'explicit': explicit?1:0,
|
||||
// 'favoriteDate': favoriteDate
|
||||
//'favoriteDate': favoriteDate
|
||||
};
|
||||
factory Track.fromSQL(Map<String, dynamic> data) => Track(
|
||||
id: data['trackId']??data['id'], //If loading from downloads table
|
||||
|
@ -174,7 +176,7 @@ class Track {
|
|||
favorite: (data['favorite'] == 1) ? true:false,
|
||||
diskNumber: data['diskNumber'],
|
||||
explicit: (data['explicit'] == 1) ? true:false,
|
||||
// favoriteDate: data['favoriteDate']
|
||||
//favoriteDate: data['favoriteDate']
|
||||
);
|
||||
|
||||
factory Track.fromJson(Map<String, dynamic> json) => _$TrackFromJson(json);
|
||||
|
@ -238,7 +240,7 @@ class Album {
|
|||
'library': (library??false)?1:0,
|
||||
'type': AlbumType.values.indexOf(type),
|
||||
'releaseDate': releaseDate,
|
||||
// 'favoriteDate': favoriteDate
|
||||
//'favoriteDate': favoriteDate
|
||||
};
|
||||
factory Album.fromSQL(Map<String, dynamic> data) => Album(
|
||||
id: data['id'],
|
||||
|
@ -255,7 +257,7 @@ class Album {
|
|||
library: (data['library'] == 1) ? true:false,
|
||||
type: AlbumType.values[data['type']],
|
||||
releaseDate: data['releaseDate'],
|
||||
// favoriteDate: data['favoriteDate']
|
||||
//favoriteDate: data['favoriteDate']
|
||||
);
|
||||
|
||||
factory Album.fromJson(Map<String, dynamic> json) => _$AlbumFromJson(json);
|
||||
|
@ -344,7 +346,7 @@ class Artist {
|
|||
'offline': off?1:0,
|
||||
'library': (library??false)?1:0,
|
||||
'radio': radio?1:0,
|
||||
// 'favoriteDate': favoriteDate
|
||||
//'favoriteDate': favoriteDate
|
||||
};
|
||||
factory Artist.fromSQL(Map<String, dynamic> data) => Artist(
|
||||
id: data['id'],
|
||||
|
@ -361,7 +363,7 @@ class Artist {
|
|||
offline: (data['offline'] == 1)?true:false,
|
||||
library: (data['library'] == 1)?true:false,
|
||||
radio: (data['radio'] == 1)?true:false,
|
||||
// favoriteDate: data['favoriteDate']
|
||||
//favoriteDate: data['favoriteDate']
|
||||
);
|
||||
|
||||
factory Artist.fromJson(Map<String, dynamic> json) => _$ArtistFromJson(json);
|
||||
|
@ -702,6 +704,8 @@ class HomePageItem {
|
|||
return HomePageItem(type: HomePageItemType.CHANNEL, value: DeezerChannel.fromPrivateJson(json));
|
||||
case 'album':
|
||||
return HomePageItem(type: HomePageItemType.ALBUM, value: Album.fromPrivateJson(json['data']));
|
||||
case 'show':
|
||||
return HomePageItem(type: HomePageItemType.SHOW, value: Show.fromPrivateJson(json['data']));
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
@ -720,6 +724,8 @@ class HomePageItem {
|
|||
return HomePageItem(type: HomePageItemType.CHANNEL, value: DeezerChannel.fromJson(json['value']));
|
||||
case 'ALBUM':
|
||||
return HomePageItem(type: HomePageItemType.ALBUM, value: Album.fromJson(json['value']));
|
||||
case 'SHOW':
|
||||
return HomePageItem(type: HomePageItemType.SHOW, value: Show.fromPrivateJson(json['value']));
|
||||
default:
|
||||
return HomePageItem();
|
||||
}
|
||||
|
@ -762,7 +768,8 @@ enum HomePageItemType {
|
|||
PLAYLIST,
|
||||
ARTIST,
|
||||
CHANNEL,
|
||||
ALBUM
|
||||
ALBUM,
|
||||
SHOW
|
||||
}
|
||||
|
||||
enum HomePageSectionLayout {
|
||||
|
@ -797,4 +804,164 @@ class DeezerLinkResponse {
|
|||
if (t == 'track') return DeezerLinkType.TRACK;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
//Sorting
|
||||
enum SortType {
|
||||
DEFAULT,
|
||||
ALPHABETIC,
|
||||
ARTIST,
|
||||
ALBUM,
|
||||
RELEASE_DATE,
|
||||
POPULARITY,
|
||||
USER,
|
||||
TRACK_COUNT,
|
||||
DATE_ADDED
|
||||
}
|
||||
|
||||
enum SortSourceTypes {
|
||||
//Library
|
||||
TRACKS,
|
||||
PLAYLISTS,
|
||||
ALBUMS,
|
||||
ARTISTS,
|
||||
|
||||
PLAYLIST
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class Sorting {
|
||||
SortType type;
|
||||
bool reverse;
|
||||
|
||||
//For preserving sorting
|
||||
String id;
|
||||
SortSourceTypes sourceType;
|
||||
|
||||
Sorting({this.type = SortType.DEFAULT, this.reverse = false, this.id, this.sourceType});
|
||||
|
||||
//Find index of sorting from cache
|
||||
static int index(SortSourceTypes type, {String id}) {
|
||||
//Empty cache
|
||||
if (cache.sorts == null) {
|
||||
cache.sorts = [];
|
||||
cache.save();
|
||||
return null;
|
||||
}
|
||||
//Find index
|
||||
int index;
|
||||
if (id != null)
|
||||
index = cache.sorts.indexWhere((s) => s.sourceType == type && s.id == id);
|
||||
else
|
||||
index = cache.sorts.indexWhere((s) => s.sourceType == type);
|
||||
if (index == -1)
|
||||
return null;
|
||||
return index;
|
||||
}
|
||||
|
||||
factory Sorting.fromJson(Map<String, dynamic> json) => _$SortingFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$SortingToJson(this);
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class Show {
|
||||
|
||||
String name;
|
||||
String description;
|
||||
ImageDetails art;
|
||||
String id;
|
||||
|
||||
Show({this.name, this.description, this.art, this.id});
|
||||
|
||||
//JSON
|
||||
factory Show.fromPrivateJson(Map<dynamic, dynamic> json) => Show(
|
||||
id: json['SHOW_ID'],
|
||||
name: json['SHOW_NAME'],
|
||||
art: ImageDetails.fromPrivateString(json['SHOW_ART_MD5'], type: 'talk'),
|
||||
description: json['SHOW_DESCRIPTION']
|
||||
);
|
||||
|
||||
factory Show.fromJson(Map<String, dynamic> json) => _$ShowFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$ShowToJson(this);
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class ShowEpisode {
|
||||
|
||||
String id;
|
||||
String title;
|
||||
String description;
|
||||
String url;
|
||||
Duration duration;
|
||||
String publishedDate;
|
||||
|
||||
ShowEpisode({this.id, this.title, this.description, this.url, this.duration, this.publishedDate});
|
||||
|
||||
String get durationString => "${duration.inMinutes}:${duration.inSeconds.remainder(60).toString().padLeft(2, '0')}";
|
||||
|
||||
//Generate MediaItem for playback
|
||||
MediaItem toMediaItem(Show show) {
|
||||
return MediaItem(
|
||||
title: title,
|
||||
displayTitle: title,
|
||||
displaySubtitle: show.name,
|
||||
album: show.name,
|
||||
id: id,
|
||||
extras: {
|
||||
'showUrl': url,
|
||||
'show': jsonEncode(show.toJson()),
|
||||
'thumb': show.art.thumb
|
||||
},
|
||||
displayDescription: description,
|
||||
duration: duration,
|
||||
artUri: show.art.full
|
||||
);
|
||||
}
|
||||
factory ShowEpisode.fromMediaItem(MediaItem mi) {
|
||||
return ShowEpisode(
|
||||
id: mi.id,
|
||||
title: mi.title,
|
||||
description: mi.displayDescription,
|
||||
url: mi.extras['showUrl'],
|
||||
duration: mi.duration,
|
||||
);
|
||||
}
|
||||
|
||||
//JSON
|
||||
factory ShowEpisode.fromPrivateJson(Map<dynamic, dynamic> json) => ShowEpisode(
|
||||
id: json['EPISODE_ID'],
|
||||
title: json['EPISODE_TITLE'],
|
||||
description: json['EPISODE_DESCRIPTION'],
|
||||
url: json['EPISODE_DIRECT_STREAM_URL'],
|
||||
duration: Duration(seconds: int.parse(json['DURATION'].toString())),
|
||||
publishedDate: json['EPISODE_PUBLISHED_TIMESTAMP']
|
||||
);
|
||||
|
||||
factory ShowEpisode.fromJson(Map<String, dynamic> json) => _$ShowEpisodeFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$ShowEpisodeToJson(this);
|
||||
}
|
||||
|
||||
class StreamQualityInfo {
|
||||
String format;
|
||||
int size;
|
||||
String source;
|
||||
|
||||
StreamQualityInfo({this.format, this.size, this.source});
|
||||
|
||||
factory StreamQualityInfo.fromJson(Map json) => StreamQualityInfo(
|
||||
format: json['format'],
|
||||
size: json['size'],
|
||||
source: json['source']
|
||||
);
|
||||
|
||||
int bitrate(Duration duration) {
|
||||
if (size == null || size == 0) return 0;
|
||||
int bitrate = (((size * 8) / 1000) / duration.inSeconds).round();
|
||||
//Round to known values
|
||||
if (bitrate > 122 && bitrate < 134)
|
||||
return 128;
|
||||
if (bitrate > 315 && bitrate < 325)
|
||||
return 320;
|
||||
return bitrate;
|
||||
}
|
||||
}
|
|
@ -32,7 +32,7 @@ Track _$TrackFromJson(Map<String, dynamic> json) {
|
|||
favorite: json['favorite'] as bool,
|
||||
diskNumber: json['diskNumber'] as int,
|
||||
explicit: json['explicit'] as bool,
|
||||
favoriteDate: json['favoriteDate'] as int,
|
||||
addedDate: json['addedDate'] as int,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,7 @@ Map<String, dynamic> _$TrackToJson(Track instance) => <String, dynamic>{
|
|||
'favorite': instance.favorite,
|
||||
'diskNumber': instance.diskNumber,
|
||||
'explicit': instance.explicit,
|
||||
'favoriteDate': instance.favoriteDate,
|
||||
'addedDate': instance.addedDate,
|
||||
'playbackDetails': instance.playbackDetails,
|
||||
};
|
||||
|
||||
|
@ -387,3 +387,81 @@ Map<String, dynamic> _$DeezerChannelToJson(DeezerChannel instance) =>
|
|||
'title': instance.title,
|
||||
'backgroundColor': DeezerChannel._colorToJson(instance.backgroundColor),
|
||||
};
|
||||
|
||||
Sorting _$SortingFromJson(Map<String, dynamic> json) {
|
||||
return Sorting(
|
||||
type: _$enumDecodeNullable(_$SortTypeEnumMap, json['type']),
|
||||
reverse: json['reverse'] as bool,
|
||||
id: json['id'] as String,
|
||||
sourceType:
|
||||
_$enumDecodeNullable(_$SortSourceTypesEnumMap, json['sourceType']),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$SortingToJson(Sorting instance) => <String, dynamic>{
|
||||
'type': _$SortTypeEnumMap[instance.type],
|
||||
'reverse': instance.reverse,
|
||||
'id': instance.id,
|
||||
'sourceType': _$SortSourceTypesEnumMap[instance.sourceType],
|
||||
};
|
||||
|
||||
const _$SortTypeEnumMap = {
|
||||
SortType.DEFAULT: 'DEFAULT',
|
||||
SortType.ALPHABETIC: 'ALPHABETIC',
|
||||
SortType.ARTIST: 'ARTIST',
|
||||
SortType.ALBUM: 'ALBUM',
|
||||
SortType.RELEASE_DATE: 'RELEASE_DATE',
|
||||
SortType.POPULARITY: 'POPULARITY',
|
||||
SortType.USER: 'USER',
|
||||
SortType.TRACK_COUNT: 'TRACK_COUNT',
|
||||
SortType.DATE_ADDED: 'DATE_ADDED',
|
||||
};
|
||||
|
||||
const _$SortSourceTypesEnumMap = {
|
||||
SortSourceTypes.TRACKS: 'TRACKS',
|
||||
SortSourceTypes.PLAYLISTS: 'PLAYLISTS',
|
||||
SortSourceTypes.ALBUMS: 'ALBUMS',
|
||||
SortSourceTypes.ARTISTS: 'ARTISTS',
|
||||
SortSourceTypes.PLAYLIST: 'PLAYLIST',
|
||||
};
|
||||
|
||||
Show _$ShowFromJson(Map<String, dynamic> json) {
|
||||
return Show(
|
||||
name: json['name'] as String,
|
||||
description: json['description'] as String,
|
||||
art: json['art'] == null
|
||||
? null
|
||||
: ImageDetails.fromJson(json['art'] as Map<String, dynamic>),
|
||||
id: json['id'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$ShowToJson(Show instance) => <String, dynamic>{
|
||||
'name': instance.name,
|
||||
'description': instance.description,
|
||||
'art': instance.art,
|
||||
'id': instance.id,
|
||||
};
|
||||
|
||||
ShowEpisode _$ShowEpisodeFromJson(Map<String, dynamic> json) {
|
||||
return ShowEpisode(
|
||||
id: json['id'] as String,
|
||||
title: json['title'] as String,
|
||||
description: json['description'] as String,
|
||||
url: json['url'] as String,
|
||||
duration: json['duration'] == null
|
||||
? null
|
||||
: Duration(microseconds: json['duration'] as int),
|
||||
publishedDate: json['publishedDate'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$ShowEpisodeToJson(ShowEpisode instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'title': instance.title,
|
||||
'description': instance.description,
|
||||
'url': instance.url,
|
||||
'duration': instance.duration?.inMicroseconds,
|
||||
'publishedDate': instance.publishedDate,
|
||||
};
|
||||
|
|
|
@ -165,9 +165,10 @@ class PlayerHelper {
|
|||
Future _loadQueuePlay(List<MediaItem> queue, String trackId) async {
|
||||
await startService();
|
||||
await settings.updateAudioServiceQuality();
|
||||
await AudioService.customAction('setIndex', queue.indexWhere((m) => m.id == trackId));
|
||||
await AudioService.updateQueue(queue);
|
||||
if (queue[0].id != trackId)
|
||||
await AudioService.skipToQueueItem(trackId);
|
||||
// if (queue[0].id != trackId)
|
||||
// await AudioService.skipToQueueItem(trackId);
|
||||
if (!AudioService.playbackState.playing)
|
||||
AudioService.play();
|
||||
}
|
||||
|
@ -236,6 +237,27 @@ class PlayerHelper {
|
|||
source: 'playlist'
|
||||
));
|
||||
}
|
||||
|
||||
//Play episode from show, load whole show as queue
|
||||
Future playShowEpisode(Show show, List<ShowEpisode> episodes, {int index = 0}) async {
|
||||
QueueSource queueSource = QueueSource(
|
||||
id: show.id,
|
||||
text: show.name,
|
||||
source: 'show'
|
||||
);
|
||||
//Generate media items
|
||||
List<MediaItem> queue = episodes.map<MediaItem>((e) => e.toMediaItem(show)).toList();
|
||||
|
||||
//Load and play
|
||||
await startService();
|
||||
await settings.updateAudioServiceQuality();
|
||||
await setQueueSource(queueSource);
|
||||
await AudioService.customAction('setIndex', index);
|
||||
await AudioService.updateQueue(queue);
|
||||
if (!AudioService.playbackState.playing)
|
||||
AudioService.play();
|
||||
}
|
||||
|
||||
//Load tracks as queue, play track id, set queue source
|
||||
Future playFromTrackList(List<Track> tracks, String trackId, QueueSource queueSource) async {
|
||||
await startService();
|
||||
|
@ -340,7 +362,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
//Quality string
|
||||
if (_queueIndex != -1 && _queueIndex < _queue.length) {
|
||||
Map extras = mediaItem.extras;
|
||||
extras['qualityString'] = event.qualityString??'';
|
||||
extras['qualityString'] = '';
|
||||
_queue[_queueIndex] = mediaItem.copyWith(extras: extras);
|
||||
}
|
||||
//Update
|
||||
|
@ -530,7 +552,6 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
this._queue = q;
|
||||
AudioServiceBackground.setQueue(_queue);
|
||||
//Load
|
||||
_queueIndex = 0;
|
||||
await _loadQueue();
|
||||
//await _player.seek(Duration.zero, index: 0);
|
||||
}
|
||||
|
@ -550,8 +571,8 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
_audioSource = ConcatenatingAudioSource(children: sources);
|
||||
//Load in just_audio
|
||||
try {
|
||||
await _player.load(_audioSource);
|
||||
await _player.seek(Duration.zero, index: qi);
|
||||
await _player.load(_audioSource, initialPosition: Duration.zero, initialIndex: qi);
|
||||
// await _player.seek(Duration.zero, index: qi);
|
||||
} catch (e) {
|
||||
//Error loading tracks
|
||||
}
|
||||
|
@ -571,9 +592,15 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
String _offlinePath = p.join((await getExternalStorageDirectory()).path, 'offline/');
|
||||
File f = File(p.join(_offlinePath, mediaItem.id));
|
||||
if (await f.exists()) {
|
||||
return f.path;
|
||||
//return f.path;
|
||||
//Stream server URL
|
||||
return 'http://localhost:36958/?id=${mediaItem.id}';
|
||||
}
|
||||
|
||||
//Show episode direct link
|
||||
if (mediaItem.extras['showUrl'] != null)
|
||||
return mediaItem.extras['showUrl'];
|
||||
|
||||
//Due to current limitations of just_audio, quality fallback moved to DeezerDataSource in ExoPlayer
|
||||
//This just returns fake url that contains metadata
|
||||
List playbackDetails = jsonDecode(mediaItem.extras['playbackDetails']);
|
||||
|
@ -583,7 +610,8 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
if (conn == ConnectivityResult.wifi) quality = wifiQuality;
|
||||
|
||||
if ((playbackDetails??[]).length < 2) return null;
|
||||
String url = 'https://dzcdn.net/?md5=${playbackDetails[0]}&mv=${playbackDetails[1]}&q=${quality.toString()}#${mediaItem.id}';
|
||||
//String url = 'https://dzcdn.net/?md5=${playbackDetails[0]}&mv=${playbackDetails[1]}&q=${quality.toString()}#${mediaItem.id}';
|
||||
String url = 'http://localhost:36958/?q=$quality&mv=${playbackDetails[1]}&md5origin=${playbackDetails[0]}&id=${mediaItem.id}';
|
||||
return url;
|
||||
}
|
||||
|
||||
|
@ -632,6 +660,10 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
AudioServiceBackground.setQueue(_queue);
|
||||
_broadcastState();
|
||||
}
|
||||
//Set index without affecting playback for loading
|
||||
if (name == 'setIndex') {
|
||||
this._queueIndex = args;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue