Skip to content

Commit

Permalink
fix(android): lowLatency bugs (closes #1176, closes #1193, closes #1165
Browse files Browse the repository at this point in the history
…) (#1272)

* fix(android): lowLatency bugs (closes #1176, closes #1193, closes #1165)

* ignoring live stream
* reset streamId on stop

* make formatter happy
  • Loading branch information
Gustl22 authored Sep 12, 2022
1 parent 0b9fed4 commit 541578c
Showing 6 changed files with 76 additions and 32 deletions.
Original file line number Diff line number Diff line change
@@ -63,9 +63,30 @@ Future<void> testControlsTab(
!audioSourceTestData.isStream &&
!isBytesSource) {
await tester.testPlayerMode(PlayerMode.lowLatency);

// Test resume
await tester.tap(find.byKey(const Key('control-resume')));
await tester.pump(const Duration(seconds: 1));
// Test pause
await tester.tap(find.byKey(const Key('control-pause')));
await tester.tap(find.byKey(const Key('control-resume')));
await tester.pump(const Duration(seconds: 1));
await tester.tap(find.byKey(const Key('control-stop')));
await tester.testPlayerMode(PlayerMode.mediaPlayer, isResume: false);
await tester.pumpAndSettle();

// Test volume
await tester.testVolume('0.5');
await tester.testVolume('1.0');

// Test release mode: loop
await tester.testReleaseMode(ReleaseMode.loop);
await tester.pump(const Duration(seconds: 3));
await tester.tap(find.byKey(const Key('control-stop')));
await tester.testReleaseMode(ReleaseMode.stop, isResume: false);
await tester.pumpAndSettle();

// Reset to media player
await tester.testPlayerMode(PlayerMode.mediaPlayer);
await tester.pumpAndSettle();
}

@@ -152,7 +173,7 @@ extension ControlsWidgetTester on WidgetTester {
}
}

Future<void> testPlayerMode(PlayerMode mode, {bool isResume = true}) async {
Future<void> testPlayerMode(PlayerMode mode) async {
printOnFailure('Test Player Mode: ${mode.name}');
await tap(find.byKey(Key('control-player-mode-${mode.name}')));
await waitFor(
@@ -161,10 +182,6 @@ extension ControlsWidgetTester on WidgetTester {
matcher: equals(mode),
),
);
if (isResume) {
await tap(find.byKey(const Key('control-resume')));
}
// TODO(Gustl22): get player mode from native implementation
}

Future<void> testReleaseMode(ReleaseMode mode, {bool isResume = true}) async {
27 changes: 25 additions & 2 deletions packages/audioplayers/example/lib/tabs/sources.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:audioplayers/audioplayers.dart';
import 'package:audioplayers_example/components/btn.dart';
import 'package:audioplayers_example/components/tab_wrapper.dart';
import 'package:audioplayers_example/components/tgl.dart';
import 'package:audioplayers_example/utils.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
@@ -26,18 +27,40 @@ class SourcesTab extends StatefulWidget {
State<SourcesTab> createState() => _SourcesTabState();
}

enum InitMode {
setSource,
play,
}

class _SourcesTabState extends State<SourcesTab>
with AutomaticKeepAliveClientMixin<SourcesTab> {
Future<void> setSource(Source source) async {
await widget.player.setSource(source);
toast('Completed setting source.', textKey: const Key('toast-source-set'));
if (initMode == InitMode.setSource) {
await widget.player.setSource(source);
toast(
'Completed setting source.',
textKey: const Key('toast-source-set'),
);
} else {
await widget.player.stop();
await widget.player.play(source);
}
}

InitMode initMode = InitMode.setSource;

@override
Widget build(BuildContext context) {
super.build(context);
return TabWrapper(
children: [
EnumTgl(
options: {for (var e in InitMode.values) 'initMode-${e.name}': e},
selected: initMode,
onChange: (InitMode m) => setState(() {
initMode = m;
}),
),
Btn(
key: const Key('setSource-url-remote-wav-1'),
txt: 'Remote URL WAV 1 - coins.wav',
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package xyz.luan.audioplayers.player

import android.media.MediaDataSource
import android.media.MediaPlayer
import android.os.Build
import android.os.PowerManager
@@ -93,4 +92,9 @@ class MediaPlayerPlayer(
override fun reset() {
mediaPlayer.reset()
}

override fun isLiveStream(): Boolean {
val duration = getDuration()
return duration == null || duration == 0
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package xyz.luan.audioplayers.player

import android.media.MediaDataSource
import xyz.luan.audioplayers.AudioContextAndroid
import xyz.luan.audioplayers.ReleaseMode
import xyz.luan.audioplayers.source.Source

interface Player {
fun getDuration(): Int?
fun getCurrentPosition(): Int?
fun isActuallyPlaying(): Boolean
fun isLiveStream(): Boolean

fun start()
fun pause()
@@ -24,4 +23,4 @@ interface Player {

fun prepare()
fun reset()
}
}
Original file line number Diff line number Diff line change
@@ -72,14 +72,20 @@ class SoundPoolPlayer(
}
}

/** The id of the sound of source which will be played */
private var soundId: Int? = null

/** The id of the stream / player */
private var streamId: Int? = null

private val urlSource: UrlSource?
get() = wrappedPlayer.source as? UrlSource

override fun stop() {
streamId?.let { soundPool.stop(it) }
streamId?.let {
soundPool.stop(it)
streamId = null
}
}

override fun release() {
@@ -169,7 +175,7 @@ class SoundPoolPlayer(
override fun seekTo(position: Int) {
if (position == 0) {
streamId?.let {
soundPool.stop(it)
stop()
if (wrappedPlayer.playing) {
soundPool.resume(it)
}
@@ -205,6 +211,8 @@ class SoundPoolPlayer(
// TODO(luan) what do I do here?
}

override fun isLiveStream() = false

/** Integer representation of the loop mode used by Android */
private fun Boolean.loopModeInteger(): Int = if (this) -1 else 0

Original file line number Diff line number Diff line change
@@ -178,15 +178,15 @@ class WrappedPlayer internal constructor(
}
if (releaseMode != ReleaseMode.RELEASE) {
pause()
if (isLiveStream()) {
if (prepared) {
if(prepared) {
if (player?.isLiveStream() == true) {
player?.stop()
prepared = false
player?.prepare()
} else {
// MediaPlayer does not allow to call player.seekTo after calling player.stop
seek(0)
}
} else {
// MediaPlayer does not allow to call player.seekTo after calling player.stop
seek(0)
}
} else {
release()
@@ -220,13 +220,11 @@ class WrappedPlayer internal constructor(
// seek operations cannot be called until after
// the player is ready.
fun seek(position: Int) {
if (!isLiveStream()) {
shouldSeekTo = if (prepared) {
player?.seekTo(position)
-1
} else {
position
}
shouldSeekTo = if (prepared && player?.isLiveStream() != true) {
player?.seekTo(position)
-1
} else {
position
}
}

@@ -240,7 +238,7 @@ class WrappedPlayer internal constructor(
player?.start()
ref.handleIsPlaying()
}
if (shouldSeekTo >= 0) {
if (shouldSeekTo >= 0 && player?.isLiveStream() != true) {
player?.seekTo(shouldSeekTo)
}
}
@@ -311,9 +309,4 @@ class WrappedPlayer internal constructor(
setLooping(isLooping)
prepare()
}

private fun isLiveStream(): Boolean {
val duration = getDuration()
return duration == null || duration == 0
}
}

0 comments on commit 541578c

Please sign in to comment.