Skip to content

Commit

Permalink
feat: youtube queue generators
Browse files Browse the repository at this point in the history
and some redesign
  • Loading branch information
MSOB7YY committed Jan 25, 2024
1 parent 8c7b45f commit 18d4892
Show file tree
Hide file tree
Showing 14 changed files with 1,222 additions and 666 deletions.
141 changes: 80 additions & 61 deletions lib/controller/generators_controller.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import 'package:history_manager/history_manager.dart';

import 'package:namida/class/track.dart';
import 'package:namida/controller/history_controller.dart';
import 'package:namida/controller/indexer_controller.dart';
import 'package:namida/core/constants.dart';
import 'package:namida/core/extensions.dart';

class NamidaGenerator {
static NamidaGenerator get inst => _instance;
static final NamidaGenerator _instance = NamidaGenerator._internal();
class NamidaGenerator extends NamidaGeneratorBase<TrackWithDate, Track> {
static final NamidaGenerator inst = NamidaGenerator._internal();
NamidaGenerator._internal();

@override
HistoryManager<TrackWithDate, Track> get historyController => HistoryController.inst;

Set<String> getHighMatcheFilesFromFilename(Iterable<String> files, String filename) {
return files.where(
(element) {
Expand All @@ -24,67 +28,12 @@ class NamidaGenerator {
).toSet();
}

List<Track> getRandomTracks([int? min, int? max]) {
final trackslist = allTracksInLibrary;
final trackslistLength = trackslist.length;

if (trackslist.length < 3) {
return [];
}

/// ignore min and max if the value is more than the alltrackslist.
if (max != null && max > allTracksInLibrary.length) {
max = null;
min = null;
}
min ??= trackslistLength ~/ 12;
max ??= trackslistLength ~/ 8;

// number of resulting tracks.
final int randomNumber = (max - min).getRandomNumberBelow(min);

final Set<Track> randomList = {};
for (int i = 0; i <= randomNumber; i++) {
randomList.add(trackslist[trackslistLength.getRandomNumberBelow()]);
}
return randomList.toList();
Iterable<Track> getRandomTracks({Track? exclude, int? min, int? max}) {
return NamidaGeneratorBase.getRandomItems(allTracksInLibrary, exclude: exclude, min: min, max: max);
}

Iterable<Track> generateRecommendedTrack(Track track) {
final historytracks = HistoryController.inst.historyTracks.toList();
if (historytracks.isEmpty) {
return [];
}
const length = 10;
final max = historytracks.length;
int clamped(int range) => range.clamp(0, max);

final Map<Track, int> numberOfListensMap = {};

for (int i = 0; i <= historytracks.length - 1;) {
final t = historytracks[i];
if (t.track == track) {
final heatTracks = historytracks.getRange(clamped(i - length), clamped(i + length)).toList();
heatTracks.loop((e, index) {
numberOfListensMap.update(e.track, (value) => value + 1, ifAbsent: () => 1);
});
// skip length since we already took 10 tracks.
i += length;
} else {
i++;
}
}

numberOfListensMap.remove(track);

final sortedByValueMap = numberOfListensMap.entries.toList();
sortedByValueMap.sortByReverse((e) => e.value);

return sortedByValueMap.map((e) => e.key);
}

List<TrackWithDate> generateTracksFromHistoryDates(DateTime? oldestDate, DateTime? newestDate, {bool removeDuplicates = true}) {
return HistoryController.inst.generateTracksFromHistoryDates(oldestDate, newestDate, removeDuplicates: removeDuplicates);
return super.generateRecommendedItemsFor(track, (current) => current.track);
}

/// [daysRange] means taking n days before [yearTimeStamp] & n days after [yearTimeStamp].
Expand Down Expand Up @@ -145,3 +94,73 @@ class NamidaGenerator {
return finalTracks;
}
}

abstract class NamidaGeneratorBase<T extends ItemWithDate, E> {
HistoryManager<T, E> get historyController;

/// Generated items listened to in a time range.
List<T> generateItemsFromHistoryDates(DateTime? oldestDate, DateTime? newestDate, {bool removeDuplicates = true}) {
return historyController.generateTracksFromHistoryDates(oldestDate, newestDate, removeDuplicates: removeDuplicates);
}

static Iterable<R> getRandomItems<R>(List<R> list, {R? exclude, int? min, int? max}) {
final itemslist = list;
final itemslistLength = itemslist.length;

if (itemslistLength <= 2) return [];

/// ignore min and max if the value is more than the alltrackslist.
if (max != null && max > itemslist.length) {
max = null;
min = null;
}
min ??= itemslistLength ~/ 12;
max ??= itemslistLength ~/ 8;

// number of resulting tracks.
final int randomNumber = (max - min).getRandomNumberBelow(min);

final randomListMap = <R, bool>{};
for (int i = 0; i <= randomNumber; i++) {
final item = list[itemslistLength.getRandomNumberBelow()];
randomListMap[item] = true;
}

if (exclude != null) randomListMap.remove(exclude);

return randomListMap.keys;
}

Iterable<E> generateRecommendedItemsFor(E item, E Function(T current) itemToSub) {
final historytracks = historyController.historyTracks.toList();
if (historytracks.isEmpty) return [];

const length = 10;
final max = historytracks.length;
int clamped(int range) => range.clamp(0, max);

final Map<E, int> numberOfListensMap = {};

for (int i = 0; i <= historytracks.length - 1;) {
final t = historytracks[i];
final subItem = itemToSub(t);
if (subItem == item) {
final heatTracks = historytracks.getRange(clamped(i - length), clamped(i + length)).toList();
heatTracks.loop((e, index) {
numberOfListensMap.update(itemToSub(e), (value) => value + 1, ifAbsent: () => 1);
});
// skip length since we already took 10 tracks.
i += length;
} else {
i++;
}
}

numberOfListensMap.remove(item);

final sortedByValueMap = numberOfListensMap.entries.toList();
sortedByValueMap.sortByReverse((e) => e.value);

return sortedByValueMap.map((e) => e.key);
}
}
24 changes: 19 additions & 5 deletions lib/controller/player_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -232,10 +232,10 @@ class Player {
bool showSnackBar = true,
String? emptyTracksMessage,
}) async {
final insertionDetails = insertionType?.toQueueInsertion();
final shouldInsertNext = insertionDetails?.insertNext ?? insertNext;
final maxCount = insertionDetails?.numberOfTracks == 0 ? null : insertionDetails?.numberOfTracks;
if (tracks.firstOrNull is Selectable) {
final insertionDetails = insertionType?.toQueueInsertion();
final shouldInsertNext = insertionDetails?.insertNext ?? insertNext;
final maxCount = insertionDetails?.numberOfTracks == 0 ? null : insertionDetails?.numberOfTracks;
final finalTracks = List<Selectable>.from(tracks.withLimit(maxCount));
insertionType?.shuffleOrSort(finalTracks);

Expand All @@ -257,11 +257,25 @@ class Player {
}
return true;
} else if (tracks.firstOrNull is YoutubeID) {
final finalVideos = List<YoutubeID>.from(tracks.withLimit(maxCount));
insertionType?.shuffleOrSortYT(finalVideos);

if (showSnackBar && finalVideos.isEmpty) {
snackyy(title: lang.NOTE, message: emptyTracksMessage ?? lang.NO_TRACKS_FOUND);
return false;
}
await _audioHandler.addToQueue(
tracks,
insertNext: insertNext,
finalVideos,
insertNext: shouldInsertNext,
insertAfterLatest: insertAfterLatest,
);
if (showSnackBar) {
final addins = shouldInsertNext ? lang.INSERTED : lang.ADDED;
snackyy(
icon: shouldInsertNext ? Broken.redo : Broken.add_circle,
message: '${addins.capitalizeFirst} ${finalVideos.length.displayVideoKeyword}',
);
}
return true;
}

Expand Down
2 changes: 1 addition & 1 deletion lib/controller/playlist_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ class PlaylistController extends PlaylistManager<TrackWithDate> {
if (rt.isEmpty) return 0;

final l = playlistsMap.keys.where((name) => name.startsWith(k_PLAYLIST_NAME_AUTO_GENERATED)).length;
addNewPlaylist('$k_PLAYLIST_NAME_AUTO_GENERATED ${l + 1}', tracks: rt);
addNewPlaylist('$k_PLAYLIST_NAME_AUTO_GENERATED ${l + 1}', tracks: rt.toList());

return rt.length;
}
Expand Down
51 changes: 34 additions & 17 deletions lib/controller/search_ports_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,23 @@ import 'dart:isolate';
import 'package:namida/core/enums.dart';
import 'package:namida/core/extensions.dart';

typedef _PortsComm = ({ReceivePort items, Completer<SendPort> search});

class SearchPortsProvider {
class SearchPortsProvider with PortsProvider {
static final SearchPortsProvider inst = SearchPortsProvider._internal();
SearchPortsProvider._internal();

final _ports = <MediaType, _PortsComm?>{};

Future<void> _disposePort(_PortsComm port) async {
port.items.close();
(await port.search.future).send('dispose');
}
final _ports = <MediaType, PortsComm?>{};

void disposeAll() {
for (final p in _ports.values) {
if (p != null) _disposePort(p);
if (p != null) disposePort(p);
}
_ports.clear();
}

Future<void> closePorts(MediaType type) async {
final port = _ports[type];
if (port != null) {
await _disposePort(port);
await disposePort(port);
_ports[type] = null;
}
}
Expand All @@ -38,13 +31,37 @@ class SearchPortsProvider {
required Future<void> Function(SendPort itemsSendPort) isolateFunction,
bool force = false,
}) async {
final portC = _ports[type];
if (portC != null && !force) return await portC.search.future;
return await preparePortRaw(
portN: _ports[type],
onPortNull: () async {
await closePorts(type);
_ports[type] = (items: ReceivePort(), search: Completer<SendPort>());
return _ports[type]!;
},
onResult: onResult,
isolateFunction: isolateFunction,
);
}
}

await closePorts(type);
_ports[type] = (items: ReceivePort(), search: Completer<SendPort>());
final port = _ports[type];
port!.items.listen((result) {
typedef PortsComm = ({ReceivePort items, Completer<SendPort> search});
mixin PortsProvider {
Future<void> disposePort(PortsComm port) async {
port.items.close();
(await port.search.future).send('dispose');
}

Future<SendPort> preparePortRaw({
required PortsComm? portN,
required Future<PortsComm> Function() onPortNull,
required void Function(dynamic result) onResult,
required Future<void> Function(SendPort itemsSendPort) isolateFunction,
bool force = false,
}) async {
if (portN != null && !force) return await portN.search.future;

final port = await onPortNull();
port.items.listen((result) {
if (result is SendPort) {
port.search.completeIfWasnt(result);
} else {
Expand Down
Loading

0 comments on commit 18d4892

Please sign in to comment.