diff --git a/audio_service/test/audio_service_test.dart b/audio_service/test/audio_service_test.dart index fe6b4c8b..effc60c5 100644 --- a/audio_service/test/audio_service_test.dart +++ b/audio_service/test/audio_service_test.dart @@ -1,745 +1,204 @@ -//import 'dart:async'; -// -//import 'package:audio_service/audio_service.dart'; -//import 'package:flutter/services.dart'; -//import 'package:flutter_test/flutter_test.dart'; +import 'package:audio_service/audio_service.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:rxdart/rxdart.dart'; void main() { - //TestWidgetsFlutterBinding.ensureInitialized(); - //final audioSessionChannel = MethodChannel('com.ryanheise.audio_session'); - - //void expectDuration(Duration a, Duration b, {int epsilon = 200}) { - // expect((a - b).inMilliseconds.abs(), lessThanOrEqualTo(epsilon)); - //} - - //setUp(() { - // audioSessionChannel.setMockMethodCallHandler((MethodCall methodCall) async { - // return null; - // }); - // MockAudioService.setup(); - //}); - - //tearDown(() { - // MockAudioService.tearDown(); - // audioSessionChannel.setMockMethodCallHandler(null); - //}); - - //test('init', () async { - // expect(AudioService.connected, equals(false)); - // await AudioService.connect(); - // expect(AudioService.connected, equals(true)); - // expect(AudioService.running, equals(false)); - // await AudioService.start(backgroundTaskEntrypoint: task1); - // expect(AudioService.running, equals(true)); - // expect(AudioServiceBackground.state.playing, equals(false)); - // expect(AudioServiceBackground.mediaItem.toString(), - // equals(_singleItem.toString())); - // await Future.delayed(Duration.zero); - // expect(AudioService.playbackState?.playing, equals(false)); - // expect(AudioService.currentMediaItem.toString(), - // equals(_singleItem.toString())); - - // await AudioService.stop(); - // expect(AudioServiceBackground.state.playing, equals(false)); - // expect(AudioService.running, equals(false)); - // expect(AudioService.connected, equals(true)); - // await AudioService.disconnect(); - // expect(AudioService.connected, equals(false)); - //}); - - //test('options', () async { - // await AudioService.connect(); - // expect(AudioService.running, equals(false)); - // final params = { - // 'param1': 'value1', - // 'param2': 2, - // 'param3': true, - // 'param4': 4.5, - // }; - // final fastForwardInterval = const Duration(seconds: 15); - // final rewindInterval = const Duration(seconds: 5); - // await AudioService.start( - // backgroundTaskEntrypoint: task1, - // params: params, - // fastForwardInterval: fastForwardInterval, - // rewindInterval: rewindInterval, - // ); - // expect( - // (await AudioService.customAction('getParams')).cast(), - // equals(params)); - // expect(await AudioService.customAction('getFastForwardInterval'), - // equals(fastForwardInterval.inMilliseconds)); - // expect(await AudioService.customAction('getRewindInterval'), - // equals(rewindInterval.inMilliseconds)); - - // await AudioService.stop(); - // await AudioService.disconnect(); - //}); - - //test('queue-modify', () async { - // await AudioService.connect(); - // await AudioService.start(backgroundTaskEntrypoint: task1); - - // await AudioService.updateQueue(_multipleItems); - // expect(AudioServiceBackground.queue, equals(_multipleItems)); - // expect(AudioService.queue, equals(_multipleItems)); - // expect(AudioServiceBackground.mediaItem.toString(), - // equals(_multipleItems[0].toString())); - - // await AudioService.addQueueItem(_singleItem); - // expect( - // AudioServiceBackground.queue, equals(_multipleItems + [_singleItem])); - // expect(AudioService.queue, equals(_multipleItems + [_singleItem])); - // expect(AudioServiceBackground.mediaItem.toString(), - // equals(_multipleItems[0].toString())); - - // await AudioService.removeQueueItem(_singleItem); - // expect(AudioServiceBackground.queue, equals(_multipleItems)); - // expect(AudioService.queue, equals(_multipleItems)); - // expect(AudioServiceBackground.mediaItem.toString(), - // equals(_multipleItems[0].toString())); - - // await AudioService.addQueueItemAt(_singleItem, 0); - // expect( - // AudioServiceBackground.queue, equals([_singleItem] + _multipleItems)); - // expect(AudioService.queue, equals([_singleItem] + _multipleItems)); - // expect(AudioServiceBackground.mediaItem.toString(), - // equals(_singleItem.toString())); - - // await AudioService.removeQueueItem(_singleItem); - // expect(AudioServiceBackground.queue, equals(_multipleItems)); - // expect(AudioService.queue, equals(_multipleItems)); - // expect(AudioServiceBackground.mediaItem.toString(), - // equals(_multipleItems[0].toString())); - - // await AudioService.addQueueItems([_singleItem, _secondItem]); - // expect(AudioServiceBackground.queue, - // equals(_multipleItems + [_singleItem, _secondItem])); - // expect(AudioService.queue, - // equals(_multipleItems + [_singleItem, _secondItem])); - // expect(AudioServiceBackground.mediaItem.toString(), - // equals(_multipleItems[0].toString())); - - // await AudioService.removeQueueItem(_singleItem); - // await AudioService.removeQueueItem(_secondItem); - // expect(AudioServiceBackground.queue, equals(_multipleItems)); - // expect(AudioService.queue, equals(_multipleItems)); - // expect(AudioServiceBackground.mediaItem.toString(), - // equals(_multipleItems[0].toString())); - - // final modified = MediaItem(id: '1', title: 'New title', album: 'album'); - // await AudioService.updateMediaItem(modified); - // expect(AudioServiceBackground.queue, - // equals([_multipleItems[0], modified, _multipleItems[2]])); - // expect(AudioService.queue, - // equals([_multipleItems[0], modified, _multipleItems[2]])); - - // await AudioService.stop(); - // expect(AudioServiceBackground.queue, equals([])); - // await AudioService.disconnect(); - //}); - - //test('queue-skip', () async { - // await AudioService.connect(); - // await AudioService.start(backgroundTaskEntrypoint: task1); - - // await AudioService.updateQueue(_multipleItems); - // expect(AudioServiceBackground.mediaItem.toString(), - // equals(_multipleItems[0].toString())); - - // await AudioService.skipToPrevious(); - // expect(AudioServiceBackground.mediaItem.toString(), - // equals(_multipleItems[0].toString())); - - // await AudioService.skipToNext(); - // expect(AudioServiceBackground.mediaItem.toString(), - // equals(_multipleItems[1].toString())); - - // await AudioService.skipToNext(); - // expect(AudioServiceBackground.mediaItem.toString(), - // equals(_multipleItems[2].toString())); - - // await AudioService.skipToNext(); - // expect(AudioServiceBackground.mediaItem.toString(), - // equals(_multipleItems[2].toString())); - - // await AudioService.skipToQueueItem(_multipleItems[1].id); - // expect(AudioServiceBackground.mediaItem.toString(), - // equals(_multipleItems[1].toString())); - - // await AudioService.stop(); - // expect(AudioServiceBackground.queue, equals([])); - // await AudioService.disconnect(); - //}); - - //test('click', () async { - // await AudioService.connect(); - // await AudioService.start(backgroundTaskEntrypoint: task1); - - // await AudioService.updateQueue(_multipleItems); - // expect(AudioServiceBackground.mediaItem.toString(), - // equals(_multipleItems[0].toString())); - - // await AudioService.click(MediaButton.next); - // expect(AudioServiceBackground.mediaItem.toString(), - // equals(_multipleItems[1].toString())); - - // await AudioService.click(MediaButton.previous); - // expect(AudioServiceBackground.mediaItem.toString(), - // equals(_multipleItems[0].toString())); - - // await AudioService.click(MediaButton.media); - // expect(AudioServiceBackground.state.playing, equals(true)); - - // await AudioService.click(MediaButton.media); - // expect(AudioServiceBackground.state.playing, equals(false)); - - // await AudioService.stop(); - // await AudioService.disconnect(); - //}); - - //test('play', () async { - // await AudioService.connect(); - // await AudioService.start(backgroundTaskEntrypoint: task1); - // await AudioService.updateQueue(_multipleItems); - // expect(AudioServiceBackground.state.playing, equals(false)); - // await Future.delayed(Duration.zero); - // expect(AudioService.playbackState.playing, equals(false)); - // await AudioService.play(); - // expect(AudioServiceBackground.state.playing, equals(true)); - // await AudioService.pause(); - // expect(AudioServiceBackground.state.playing, equals(false)); - // expect(AudioServiceBackground.mediaItem.toString(), - // equals(_multipleItems[0].toString())); - // await AudioService.playFromMediaId(_multipleItems[2].id); - // expect(AudioServiceBackground.mediaItem.toString(), - // equals(_multipleItems[2].toString())); - // await AudioService.playMediaItem(_singleItem); - // expect(AudioServiceBackground.mediaItem.toString(), - // equals(_singleItem.toString())); - - // await AudioService.stop(); - // expect(AudioServiceBackground.state.playing, equals(false)); - // await AudioService.disconnect(); - //}); - - //test('shuffle', () async { - // await AudioService.connect(); - // await AudioService.start(backgroundTaskEntrypoint: task1); - // expect(AudioServiceBackground.state.shuffleMode, - // equals(AudioServiceShuffleMode.none)); - // expect(AudioService.playbackState.shuffleMode, - // equals(AudioServiceShuffleMode.none)); - - // await AudioService.setShuffleMode(AudioServiceShuffleMode.all); - // expect(AudioServiceBackground.state.shuffleMode, - // equals(AudioServiceShuffleMode.all)); - // expect(AudioService.playbackState.shuffleMode, - // equals(AudioServiceShuffleMode.all)); - - // await AudioService.stop(); - // expect(AudioServiceBackground.state.shuffleMode, - // equals(AudioServiceShuffleMode.none)); - // await AudioService.disconnect(); - //}); - - //test('repeat', () async { - // await AudioService.connect(); - // await AudioService.start(backgroundTaskEntrypoint: task1); - // expect(AudioServiceBackground.state.repeatMode, - // equals(AudioServiceRepeatMode.none)); - // expect(AudioService.playbackState.repeatMode, - // equals(AudioServiceRepeatMode.none)); - - // await AudioService.setRepeatMode(AudioServiceRepeatMode.all); - // expect(AudioServiceBackground.state.repeatMode, - // equals(AudioServiceRepeatMode.all)); - // expect(AudioService.playbackState.repeatMode, - // equals(AudioServiceRepeatMode.all)); - - // await AudioService.stop(); - // expect(AudioServiceBackground.state.repeatMode, - // equals(AudioServiceRepeatMode.none)); - // await AudioService.disconnect(); - //}); - - //test('MediaItem', () async { - // final item = MediaItem( - // id: 'id', - // album: 'album', - // title: 'title', - // artist: 'artist', - // genre: 'genre', - // duration: Duration.zero, - // artUri: 'https://foo.foo/foo.mp3', - // playable: true, - // displayTitle: 'displayTitle', - // displaySubtitle: 'displaySubtitle', - // displayDescription: 'displayDescription', - // rating: Rating.newHeartRating(true), - // extras: {'a': 'a', 'b': 2}, - // ); - // final expectedItem2 = MediaItem( - // id: 'id', - // album: 'album', - // title: 'title', - // artist: 'artist', - // genre: 'genre', - // duration: Duration(minutes: 1), - // artUri: 'https://foo.foo/foo.mp3', - // playable: true, - // displayTitle: 'displayTitle', - // displaySubtitle: 'displaySubtitle', - // displayDescription: 'displayDescription', - // rating: Rating.newHeartRating(true), - // extras: {'a': 'a', 'b': 2}, - // ); - // final expectedItem3 = MediaItem( - // id: 'id', - // album: 'album', - // title: 'new title', - // artist: 'artist', - // genre: 'genre', - // duration: Duration.zero, - // artUri: 'https://foo.foo/foo.mp3', - // playable: true, - // displayTitle: 'displayTitle', - // displaySubtitle: 'displaySubtitle', - // displayDescription: 'displayDescription', - // rating: Rating.newHeartRating(true), - // extras: {'a': 'a', 'b': 2}, - // ); - // final item2 = item.copyWith(duration: Duration(minutes: 1)); - // expect(item2.toString(), equals(expectedItem2.toString())); - // final item3 = item.copyWith(title: 'new title'); - // expect(item3.toString(), equals(expectedItem3.toString())); - //}); - - //test('rating', () async { - // for (var heart in [true, false]) { - // final rating = Rating.newHeartRating(heart); - // expect(rating.getRatingStyle(), equals(RatingStyle.heart)); - // expect(rating.hasHeart(), equals(heart)); - // expect(rating.isRated(), equals(true)); - // } - - // for (var maxStars = 3; maxStars <= 5; maxStars++) { - // final style = RatingStyle.values[maxStars]; - // for (var stars = 0; stars <= maxStars; stars++) { - // final rating = Rating.newStarRating(style, stars); - // expect(rating.getRatingStyle(), equals(style)); - // expect(rating.getStarRating(), equals(stars)); - // expect(rating.isRated(), equals(true)); - // } - // expect(() => Rating.newStarRating(style, -1), throwsArgumentError); - // expect( - // () => Rating.newStarRating(style, maxStars + 1), throwsArgumentError); - // } - // for (var style in [ - // RatingStyle.none, - // RatingStyle.heart, - // RatingStyle.thumbUpDown, - // RatingStyle.percentage, - // ]) { - // expect(() => Rating.newStarRating(style, 3), throwsArgumentError); - // } - - // for (var thumb in [true, false]) { - // final rating = Rating.newThumbRating(thumb); - // expect(rating.getRatingStyle(), equals(RatingStyle.thumbUpDown)); - // expect(rating.isThumbUp(), equals(thumb)); - // expect(rating.isRated(), equals(true)); - // } - - // for (var percent in [0.0, 45.5, 100.0]) { - // final rating = Rating.newPercentageRating(percent); - // expect(rating.getRatingStyle(), equals(RatingStyle.percentage)); - // expect(rating.getPercentRating(), equals(percent)); - // expect(rating.isRated(), equals(true)); - // } - // expect(() => Rating.newPercentageRating(-1.0), throwsArgumentError); - // expect(() => Rating.newPercentageRating(100.1), throwsArgumentError); - - // for (var style in RatingStyle.values) { - // final rating = Rating.newUnratedRating(style); - // expect(rating.getRatingStyle(), equals(style)); - // expect(rating.isRated(), equals(false)); - // } - //}); - - //test('positionStream', () async { - // final period = Duration(seconds: 3); - // final position1 = period; - // final position2 = position1 + period; - // final speed1 = 0.75; - // final speed2 = 1.5; - // final stepDuration = period ~/ 5; - // var target = stepDuration; - // await AudioService.connect(); - // await AudioService.start(backgroundTaskEntrypoint: task1); - // await AudioService.setSpeed(speed1); - // AudioService.play(); - // final stopwatch = Stopwatch(); - // stopwatch.start(); - // var completer = Completer(); - // StreamSubscription subscription; - // subscription = AudioService.positionStream.listen((position) { - // if (position >= position1) { - // subscription.cancel(); - // completer.complete(); - // } else if (position >= target) { - // expectDuration(position, stopwatch.elapsed * speed1); - // target += stepDuration; - // } - // }); - // await completer.future; - // await AudioService.setSpeed(speed2); - // stopwatch.reset(); - // target = position1 + target; - // completer = Completer(); - // subscription = AudioService.positionStream.listen((position) { - // if (position >= position2) { - // subscription.cancel(); - // completer.complete(); - // } else if (position >= target) { - // expectDuration(position, position1 + stopwatch.elapsed * speed2); - // target += stepDuration; - // } - // }); - // await completer.future; - // await AudioService.stop(); - // await AudioService.disconnect(); - //}); - - //test('customEventStream', () async { - // await AudioService.connect(); - // await AudioService.start(backgroundTaskEntrypoint: task1); - // Duration position; - // final subscription = AudioService.customEventStream.listen((event) { - // position = event; - // }); - // for (var seconds in [47, 123]) { - // final testPosition = Duration(seconds: seconds); - // await AudioService.seekTo(testPosition); - // await Future.delayed(Duration(milliseconds: 100)); - // expect(position, equals(testPosition)); - // } - // await AudioService.stop(); - // await AudioService.disconnect(); - // subscription.cancel(); - //}); + TestWidgetsFlutterBinding.ensureInitialized(); + + group('BaseAudioHandler:', () { + test('Constructor sets correct default parameter values', () { + final audioHandler = BaseAudioHandler(); + + final actual = audioHandler.playbackState.nvalue!; + final expected = PlaybackState(updateTime: actual.updateTime); + expect(actual, equals(expected)); + final timeNow = DateTime.now().millisecond; + expect(actual.updateTime.millisecond, closeTo(timeNow, 1000)); + + final queue = audioHandler.queue.nvalue; + expect(queue, equals([])); + + final queueTitle = audioHandler.queueTitle.nvalue; + expect(queueTitle, equals('')); + + final mediaItem = audioHandler.mediaItem.nvalue; + expect(mediaItem, isNull); + + final androidPlaybackInfo = audioHandler.androidPlaybackInfo; + expect(androidPlaybackInfo, isA>()); + + final ratingStyle = audioHandler.ratingStyle; + expect(ratingStyle, isA>()); + + final customEvent = audioHandler.customEvent; + expect(customEvent, isA>()); + + final customState = audioHandler.customState; + expect(customState, isA>()); + }); + + test('click() default logic works', () async { + final audioHandler = _TestableBaseAudioHandler(); + + // was paused, MediaButton.media clicked + await audioHandler.click(); + expect(audioHandler.playbackState.nvalue!.playing, false); + expect(audioHandler.playCount, equals(1)); + expect(audioHandler.pauseCount, equals(0)); + expect(audioHandler.skipToNextCount, equals(0)); + expect(audioHandler.skipToPreviousCount, equals(0)); + + // was playing, MediaButton.media clicked + audioHandler.reset(); + audioHandler.playbackState.add(PlaybackState(playing: true)); + await audioHandler.click(); + expect(audioHandler.playCount, equals(0)); + expect(audioHandler.pauseCount, equals(1)); + expect(audioHandler.skipToNextCount, equals(0)); + expect(audioHandler.skipToPreviousCount, equals(0)); + + // MediaButton.next + audioHandler.reset(); + await audioHandler.click(MediaButton.next); + expect(audioHandler.playCount, equals(0)); + expect(audioHandler.pauseCount, equals(0)); + expect(audioHandler.skipToNextCount, equals(1)); + expect(audioHandler.skipToPreviousCount, equals(0)); + + // MediaButton.previous + audioHandler.reset(); + await audioHandler.click(MediaButton.previous); + expect(audioHandler.playCount, equals(0)); + expect(audioHandler.pauseCount, equals(0)); + expect(audioHandler.skipToNextCount, equals(0)); + expect(audioHandler.skipToPreviousCount, equals(1)); + }); + + test('other methods return expected default values', () async { + final audioHandler = BaseAudioHandler(); + + await audioHandler.stop(); + expect(audioHandler.playbackState.nvalue!.processingState, + AudioProcessingState.idle); + + final children = await audioHandler.getChildren('parentMediaId'); + expect(children, equals([])); + + final mediaItem = await audioHandler.getMediaItem('mediaId'); + expect(mediaItem, isNull); + + final results = await audioHandler.search('query'); + expect(results, equals([])); + }); + }); + + group('QueueHandler:', () { + test('able to modify media items in queue', () { + // setup + final handler = _TestableQueueHandler(); + final mediaItem = const MediaItem(id: '0', title: 'title'); + + // add single item + expect(handler.queue.nvalue?.length, equals(0)); + handler.addQueueItem(mediaItem); + expect(handler.queue.nvalue?.length, equals(1)); + + // add multiple items + handler.addQueueItems([ + mediaItem.copyWith(id: '1'), + mediaItem.copyWith(id: '2'), + ]); + expect(handler.queue.nvalue?.length, equals(3)); + + // insert item + handler.insertQueueItem(1, mediaItem.copyWith(id: 'inserted')); + expect(handler.queue.nvalue?.length, equals(4)); + expect(handler.queue.nvalue?[1].id, 'inserted'); + + // update item + expect(handler.queue.nvalue?[0].id, '0'); + expect(handler.queue.nvalue?[0].album, null); + handler.updateMediaItem(mediaItem.copyWith(album: 'abc')); + expect(handler.queue.nvalue?.length, equals(4)); + expect(handler.queue.nvalue?[0].id, '0'); + expect(handler.queue.nvalue?[0].album, 'abc'); + + // remove item + handler.removeQueueItem(mediaItem); + expect(handler.queue.nvalue?.length, equals(3)); + + // replace queue + handler.updateQueue([mediaItem]); + expect(handler.queue.nvalue?.length, equals(1)); + }); + + test('skipping works', () async { + final handler = _TestableQueueHandler(); + final mediaItem1 = const MediaItem(id: '1', title: 'title'); + final mediaItem2 = const MediaItem(id: '2', title: 'title'); + handler.addQueueItems([mediaItem1, mediaItem2]); + + await handler.skipToQueueItem(0); + expect(handler.mediaItem.nvalue, equals(mediaItem1)); + + await handler.skipToNext(); + expect(handler.mediaItem.nvalue, equals(mediaItem2)); + + await handler.skipToPrevious(); + expect(handler.mediaItem.nvalue, equals(mediaItem1)); + }); + }); } -//final _singleItem = MediaItem( -// id: 'single', -// title: 'Single', -// album: 'album', -//); -//final _secondItem = MediaItem( -// id: 'second', -// title: 'Second', -// album: 'album', -// duration: Duration(minutes: 2), -//); -//final _multipleItems = List.generate( -// 3, (i) => MediaItem(id: '$i', title: 'title $i', album: 'album')); -// -//void task1() => AudioServiceBackground.run(() => Task1()); -// -//class Task1 extends BackgroundAudioTask { -// final _queue = [_singleItem]; -// var _index = 0; -// Map _params; -// MediaItem get mediaItem => _index < _queue.length ? _queue[_index] : null; -// -// @override -// Future onStart(Map params) async { -// _params = params; -// await AudioServiceBackground.setQueue(_queue); -// await AudioServiceBackground.setState( -// processingState: AudioProcessingState.ready); -// await AudioServiceBackground.setMediaItem(mediaItem); -// } -// -// @override -// Future onPause() async { -// AudioServiceBackground.setState(playing: false); -// } -// -// @override -// Future onPrepare() async {} -// -// @override -// Future onPrepareFromMediaId(String mediaId) async {} -// -// @override -// Future onPlay() async { -// await AudioServiceBackground.setState(playing: true); -// } -// -// @override -// Future onPlayFromMediaId(String mediaId) async { -// _index = _queue.indexWhere((item) => item.id == mediaId); -// await AudioServiceBackground.setState(playing: true); -// await AudioServiceBackground.setMediaItem(mediaItem); -// } -// -// @override -// Future onPlayMediaItem(MediaItem mediaItem) async { -// AudioServiceBackground.setState(playing: true); -// await AudioServiceBackground.setMediaItem(mediaItem); -// } -// -// @override -// Future onAddQueueItem(MediaItem mediaItem) async { -// _queue.add(mediaItem); -// await AudioServiceBackground.setQueue(_queue); -// } -// -// @override -// Future onUpdateQueue(List queue) async { -// final oldMediaItem = mediaItem; -// _queue.replaceRange(0, _queue.length, queue); -// await AudioServiceBackground.setQueue(_queue); -// if (mediaItem != oldMediaItem) { -// await AudioServiceBackground.setMediaItem(mediaItem); -// } -// } -// -// @override -// Future onUpdateMediaItem(MediaItem mediaItem) async { -// final index = _queue.indexOf(mediaItem); -// if (index != -1) { -// _queue[index] = mediaItem; -// await AudioServiceBackground.setQueue(_queue); -// } -// } -// -// @override -// Future onAddQueueItemAt(MediaItem mediaItem, int index) async { -// final oldMediaItem = this.mediaItem; -// _queue.insert(index, mediaItem); -// await AudioServiceBackground.setQueue(_queue); -// if (this.mediaItem != oldMediaItem) { -// await AudioServiceBackground.setMediaItem(this.mediaItem); -// } -// } -// -// @override -// Future onRemoveQueueItem(MediaItem mediaItem) async { -// final oldMediaItem = this.mediaItem; -// _queue.remove(mediaItem); -// await AudioServiceBackground.setQueue(_queue); -// if (this.mediaItem != oldMediaItem) { -// // Note: This is not a robust way to implement a queue, but our -// // purpose is not to build a complete background task, rather it -// // is to test the plugin proper. -// await AudioServiceBackground.setMediaItem(this.mediaItem); -// } -// } -// -// @override -// Future onFastForward() async {} -// -// @override -// Future onRewind() async {} -// -// @override -// Future onSkipToQueueItem(String mediaId) async { -// final mediaItem = _queue.firstWhere((item) => item.id == mediaId); -// await AudioServiceBackground.setMediaItem(mediaItem); -// } -// -// @override -// Future onSeekTo(Duration position) async { -// await AudioServiceBackground.setState(position: position); -// AudioServiceBackground.sendCustomEvent(position); -// } -// -// @override -// Future onSetRating(Rating rating, Map extras) async {} -// -// @override -// Future onSetRepeatMode(AudioServiceRepeatMode repeatMode) async { -// await AudioServiceBackground.setState(repeatMode: repeatMode); -// } -// -// @override -// Future onSetShuffleMode(AudioServiceShuffleMode shuffleMode) async { -// await AudioServiceBackground.setState(shuffleMode: shuffleMode); -// } -// -// @override -// Future onSeekBackward(bool begin) async {} -// -// @override -// Future onSeekForward(bool begin) async {} -// -// @override -// Future onSetSpeed(double speed) async { -// await AudioServiceBackground.setState(speed: speed); -// } -// -// @override -// Future onCustomAction(String name, dynamic arguments) async { -// switch (name) { -// case 'getParams': -// return _params; -// case 'getFastForwardInterval': -// return fastForwardInterval.inMilliseconds; -// case 'getRewindInterval': -// return rewindInterval.inMilliseconds; -// default: -// return [name, arguments]; -// } -// } -// -// @override -// Future onTaskRemoved() async { -// await onStop(); -// } -//} -// -//class MockAudioService { -// static final channel = MethodChannel('ryanheise.com/audioService'); -// static final channelInverse = -// MethodChannel('ryanheise.com/audioServiceInverse'); -// static final bgChannel = -// MethodChannel('ryanheise.com/audioServiceBackground'); -// static final bgChannelInverse = -// MethodChannel('ryanheise.com/audioServiceBackgroundInverse'); -// -// static var _connected = false; -// static var _running = false; -// static int _fastForwardInterval; -// static int _rewindInterval; -// static Map _params; -// static Completer _stopCompleter; -// -// static void setup() { -// _connected = false; -// _running = false; -// _fastForwardInterval = null; -// _rewindInterval = null; -// _params = null; -// _stopCompleter = null; -// channel.setMockMethodCallHandler((call) async { -// final onMethod = -// 'on' + call.method[0].toUpperCase() + call.method.substring(1); -// final args = call.arguments; -// switch (call.method) { -// case 'isRunning': -// return _running; -// case 'start': -// if (_running) return false; -// _params = (args['params'] as Map)?.cast(); -// _fastForwardInterval = args['fastForwardInterval']; -// _rewindInterval = args['rewindInterval']; -// _running = true; -// return true; -// case 'connect': -// if (_connected) return false; -// _connected = true; -// return true; -// case 'disconnect': -// _connected = false; -// return true; -// case 'setBrowseMediaParent': -// return true; -// case 'addQueueItem': -// case 'removeQueueItem': -// case 'updateQueue': -// case 'updateMediaItem': -// case 'playMediaItem': -// case 'click': -// case 'prepareFromMediaId': -// case 'playFromMediaId': -// case 'skipToQueueItem': -// case 'seekTo': -// case 'setRepeatMode': -// case 'setShuffleMode': -// case 'setSpeed': -// case 'seekForward': -// case 'seekBackward': -// return bgChannelInverse.invokeMethod(onMethod, [args]); -// case 'addQueueItemAt': -// case 'prepare': -// case 'play': -// case 'pause': -// case 'skipToNext': -// case 'skipToPrevious': -// case 'fastForward': -// case 'rewind': -// return bgChannelInverse.invokeMethod(onMethod, args); -// case 'stop': -// bgChannelInverse.invokeMethod(onMethod, args); -// _stopCompleter = Completer(); -// return _stopCompleter.future; -// case 'setRating': -// return bgChannelInverse -// .invokeMethod(onMethod, [args['rating'], args['extras']]); -// default: -// return bgChannelInverse.invokeMethod(call.method, args); -// } -// }); -// bgChannel.setMockMethodCallHandler((call) async { -// final args = call.arguments; -// switch (call.method) { -// case 'ready': -// return { -// 'fastForwardInterval': _fastForwardInterval, -// 'rewindInterval': _rewindInterval, -// 'params': _params, -// }; -// case 'started': -// return true; -// case 'setMediaItem': -// await channelInverse.invokeMethod('onMediaChanged', [args]); -// return true; -// case 'setQueue': -// await channelInverse.invokeMethod('onQueueChanged', [args]); -// return true; -// case 'setState': -// final rawControls = args[0]; -// final rawSystemActions = args[1]; -// var actionBits = 0; -// for (var rawControl in rawControls) { -// actionBits |= (1 << rawControl["action"]); -// } -// for (var rawSystemAction in rawSystemActions) { -// actionBits |= 1 << rawSystemAction; -// } -// int updateTime = args[7]; -// if (updateTime == null) { -// updateTime = DateTime.now().millisecondsSinceEpoch; -// } -// await channelInverse.invokeMethod('onPlaybackStateChanged', [ -// args[2], // processingState -// args[3], // playing -// actionBits, -// args[4], // position -// args[5], // bufferedPosition -// args[6], // speed -// updateTime, -// args[9], // repeatMode -// args[10], // shuffleMode -// ]); -// return true; -// case 'stopped': -// _running = false; -// await channelInverse.invokeMethod('onStopped'); -// _stopCompleter.complete(true); -// return true; -// case 'notifyChildrenChanged': -// return true; -// case 'androidForceEnableMediaButtons': -// return true; -// } -// }); -// } -// -// static void tearDown() { -// channel.setMockMethodCallHandler(null); -// bgChannel.setMockMethodCallHandler(null); -// } -//} +class _TestableBaseAudioHandler extends BaseAudioHandler { + var pauseCount = 0; + var playCount = 0; + var skipToNextCount = 0; + var skipToPreviousCount = 0; + + void reset() { + pauseCount = 0; + playCount = 0; + skipToNextCount = 0; + skipToPreviousCount = 0; + } + + @override + Future pause() { + pauseCount++; + return super.pause(); + } + + @override + Future play() { + playCount++; + return super.play(); + } + + @override + Future skipToNext() { + skipToNextCount++; + return super.skipToNext(); + } + + @override + Future skipToPrevious() { + skipToPreviousCount++; + return super.skipToPrevious(); + } +} + +class _TestableQueueHandler extends BaseAudioHandler with QueueHandler { + @override + Future skipToQueueItem(int index) async { + playbackState.add(playbackState.nvalue!.copyWith(queueIndex: index)); + mediaItem.add(queue.value![index]); + await super.skipToQueueItem(index); + } +} + +/// Backwards compatible extensions on rxdart's ValueStream +extension _ValueStreamExtension on ValueStream { + /// Backwards compatible version of valueOrNull. + T? get nvalue => hasValue ? value : null; +}