support dynamic duration (#104)

* add duration in PlaybackEvent

* emit duration updates on dynamic length sources

* Merge branch 'master' of https://github.com/ryanheise/just_audio into pr/android-duration

# Conflicts:
#	lib/just_audio.dart

* fix merge

* update _durationFuture
This commit is contained in:
LKHO 2020-06-03 18:38:08 +08:00 committed by GitHub
parent bc766e834c
commit ee0c4cd7cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 137 additions and 66 deletions

View File

@ -1,11 +1,13 @@
package com.ryanheise.just_audio; package com.ryanheise.just_audio;
import android.content.Context;
import android.net.Uri;
import android.os.Handler; import android.os.Handler;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.MetadataOutput; import com.google.android.exoplayer2.metadata.MetadataOutput;
@ -21,10 +23,15 @@ import com.google.android.exoplayer2.source.hls.HlsMediaSource;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource; import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import io.flutter.Log; import io.flutter.Log;
import io.flutter.plugin.common.EventChannel; import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.EventChannel.EventSink; import io.flutter.plugin.common.EventChannel.EventSink;
@ -34,14 +41,6 @@ import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result; import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.common.PluginRegistry.Registrar; import io.flutter.plugin.common.PluginRegistry.Registrar;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import android.content.Context;
import android.net.Uri;
import java.util.List;
public class AudioPlayer implements MethodCallHandler, Player.EventListener, MetadataOutput { public class AudioPlayer implements MethodCallHandler, Player.EventListener, MetadataOutput {
static final String TAG = "AudioPlayer"; static final String TAG = "AudioPlayer";
@ -76,12 +75,11 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Met
private final Runnable bufferWatcher = new Runnable() { private final Runnable bufferWatcher = new Runnable() {
@Override @Override
public void run() { public void run() {
long newBufferedPosition = Math.min(duration, player.getBufferedPosition()); long newBufferedPosition = player.getBufferedPosition();
if (newBufferedPosition != bufferedPosition) { if (newBufferedPosition != bufferedPosition) {
bufferedPosition = newBufferedPosition; bufferedPosition = newBufferedPosition;
broadcastPlaybackEvent(); broadcastPlaybackEvent();
} }
if (duration > 0 && newBufferedPosition >= duration) return;
if (buffering) { if (buffering) {
handler.postDelayed(this, 200); handler.postDelayed(this, 200);
} else if (state == PlaybackState.playing) { } else if (state == PlaybackState.playing) {
@ -161,7 +159,7 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Met
switch (playbackState) { switch (playbackState) {
case Player.STATE_READY: case Player.STATE_READY:
if (prepareResult != null) { if (prepareResult != null) {
duration = player.getDuration(); duration = getDuration();
justConnected = true; justConnected = true;
transition(PlaybackState.stopped); transition(PlaybackState.stopped);
prepareResult.success(duration); prepareResult.success(duration);
@ -301,6 +299,7 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Met
event.add(updateTime = System.currentTimeMillis()); event.add(updateTime = System.currentTimeMillis());
event.add(Math.max(updatePosition, bufferedPosition)); event.add(Math.max(updatePosition, bufferedPosition));
event.add(collectIcyMetadata()); event.add(collectIcyMetadata());
event.add(duration = getDuration());
if (eventSink != null) { if (eventSink != null) {
eventSink.success(event); eventSink.success(event);
@ -344,6 +343,14 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Met
} }
} }
private long getDuration() {
if (state == PlaybackState.none || state == PlaybackState.connecting) {
return C.TIME_UNSET;
} else {
return player.getDuration();
}
}
private void setError(String errorCode, String errorMsg) { private void setError(String errorCode, String errorMsg) {
if (prepareResult != null) { if (prepareResult != null) {
prepareResult.error(errorCode, errorMsg, null); prepareResult.error(errorCode, errorMsg, null);

View File

@ -1,41 +1,62 @@
# Generated by pub # Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile # See https://dart.dev/tools/pub/glossary#lockfile
packages: packages:
archive:
dependency: transitive
description:
name: archive
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.11"
args:
dependency: transitive
description:
name: args
url: "https://pub.dartlang.org"
source: hosted
version: "1.5.2"
async: async:
dependency: transitive dependency: transitive
description: description:
name: async name: async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.4.1" version: "2.4.0"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
name: boolean_selector name: boolean_selector
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.0" version: "1.0.5"
charcode: charcode:
dependency: transitive dependency: transitive
description: description:
name: charcode name: charcode
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.3" version: "1.1.2"
clock:
dependency: transitive
description:
name: clock
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
collection: collection:
dependency: transitive dependency: transitive
description: description:
name: collection name: collection
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.14.12" version: "1.14.11"
convert:
dependency: transitive
description:
name: convert
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.1"
crypto:
dependency: transitive
description:
name: crypto
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.3"
cupertino_icons: cupertino_icons:
dependency: "direct main" dependency: "direct main"
description: description:
@ -43,13 +64,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.1.2" version: "0.1.2"
fake_async:
dependency: transitive
description:
name: fake_async
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -65,13 +79,20 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
image:
dependency: transitive
description:
name: image
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.4"
just_audio: just_audio:
dependency: "direct dev" dependency: "direct dev"
description: description:
path: ".." path: ".."
relative: true relative: true
source: path source: path
version: "0.1.6" version: "0.1.10"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
@ -92,7 +113,7 @@ packages:
name: path name: path
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.7.0" version: "1.6.4"
path_provider: path_provider:
dependency: transitive dependency: transitive
description: description:
@ -100,6 +121,20 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.5.1" version: "1.5.1"
pedantic:
dependency: transitive
description:
name: pedantic
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0+1"
petitparser:
dependency: transitive
description:
name: petitparser
url: "https://pub.dartlang.org"
source: hosted
version: "2.4.0"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@ -107,13 +142,20 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.2.1" version: "2.2.1"
quiver:
dependency: transitive
description:
name: quiver
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.5"
rxdart: rxdart:
dependency: "direct main" dependency: "direct main"
description: description:
name: rxdart name: rxdart
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.24.0" version: "0.24.1"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -125,7 +167,7 @@ packages:
name: source_span name: source_span
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.7.0" version: "1.5.5"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -160,7 +202,7 @@ packages:
name: test_api name: test_api
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.15" version: "0.2.11"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@ -175,6 +217,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.8" version: "2.0.8"
xml:
dependency: transitive
description:
name: xml
url: "https://pub.dartlang.org"
source: hosted
version: "3.5.0"
sdks: sdks:
dart: ">=2.6.0 <3.0.0" dart: ">=2.6.0 <3.0.0"
flutter: ">=1.12.8 <2.0.0" flutter: ">=1.12.8 <2.0.0"

View File

@ -74,8 +74,6 @@ class AudioPlayer {
final int _id; final int _id;
Duration _duration;
Future<Duration> _durationFuture; Future<Duration> _durationFuture;
final _durationSubject = BehaviorSubject<Duration>(); final _durationSubject = BehaviorSubject<Duration>();
@ -131,26 +129,32 @@ class AudioPlayer {
AudioPlayer._internal(this._id) : _channel = _init(_id) { AudioPlayer._internal(this._id) : _channel = _init(_id) {
_eventChannelStream = EventChannel('com.ryanheise.just_audio.events.$_id') _eventChannelStream = EventChannel('com.ryanheise.just_audio.events.$_id')
.receiveBroadcastStream() .receiveBroadcastStream()
.map((data) => _audioPlaybackEvent = AudioPlaybackEvent( .map((data) {
state: AudioPlaybackState.values[data[0]], final duration =
buffering: data[1], Duration(milliseconds: data.length < 7 || data[6] < 0 ? -1 : data[6]);
updatePosition: Duration(milliseconds: data[2]), _durationFuture = Future.value(duration);
updateTime: Duration(milliseconds: data[3]), _durationSubject.add(duration);
bufferedPosition: Duration(milliseconds: data[4]), return _audioPlaybackEvent = AudioPlaybackEvent(
speed: _speed, state: AudioPlaybackState.values[data[0]],
duration: _duration, buffering: data[1],
icyMetadata: data.length < 6 || data[5] == null updatePosition: Duration(milliseconds: data[2]),
? null updateTime: Duration(milliseconds: data[3]),
: IcyMetadata( bufferedPosition: Duration(milliseconds: data[4]),
info: IcyInfo(title: data[5][0][0], url: data[5][0][1]), speed: _speed,
headers: IcyHeaders( duration: duration,
bitrate: data[5][1][0], icyMetadata: data.length < 6 || data[5] == null
genre: data[5][1][1], ? null
name: data[5][1][2], : IcyMetadata(
metadataInterval: data[5][1][3], info: IcyInfo(title: data[5][0][0], url: data[5][0][1]),
url: data[5][1][4], headers: IcyHeaders(
isPublic: data[5][1][5])), bitrate: data[5][1][0],
)); genre: data[5][1][1],
name: data[5][1][2],
metadataInterval: data[5][1][3],
url: data[5][1][4],
isPublic: data[5][1][5])),
);
});
_eventChannelStreamSubscription = _eventChannelStream.listen( _eventChannelStreamSubscription = _eventChannelStream.listen(
_playbackEventSubject.add, _playbackEventSubject.add,
onError: _playbackEventSubject.addError); onError: _playbackEventSubject.addError);
@ -253,11 +257,13 @@ class AudioPlayer {
/// https://somewhere.com/somestream?x=etc#.m3u8 /// https://somewhere.com/somestream?x=etc#.m3u8
Future<Duration> setUrl(final String url) async { Future<Duration> setUrl(final String url) async {
try { try {
_durationFuture = _invokeMethod('setUrl', [url]) _durationFuture = _invokeMethod('setUrl', [url]).then((ms) =>
.then((ms) => ms == null ? null : Duration(milliseconds: ms)); (ms == null || ms < 0)
_duration = await _durationFuture; ? const Duration(milliseconds: -1)
_durationSubject.add(_duration); : Duration(milliseconds: ms));
return _duration; final duration = await _durationFuture;
_durationSubject.add(duration);
return duration;
} on PlatformException catch (e) { } on PlatformException catch (e) {
return Future.error(e.message); return Future.error(e.message);
} }
@ -294,7 +300,9 @@ class AudioPlayer {
Future<Duration> setClip({Duration start, Duration end}) async { Future<Duration> setClip({Duration start, Duration end}) async {
_durationFuture = _durationFuture =
_invokeMethod('setClip', [start?.inMilliseconds, end?.inMilliseconds]) _invokeMethod('setClip', [start?.inMilliseconds, end?.inMilliseconds])
.then((ms) => ms == null ? null : Duration(milliseconds: ms)); .then((ms) => (ms == null || ms < 0)
? const Duration(milliseconds: -1)
: Duration(milliseconds: ms));
final duration = await _durationFuture; final duration = await _durationFuture;
_durationSubject.add(duration); _durationSubject.add(duration);
return duration; return duration;

View File

@ -107,6 +107,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.5.1" version: "1.5.1"
pedantic:
dependency: transitive
description:
name: pedantic
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0+1"
petitparser: petitparser:
dependency: transitive dependency: transitive
description: description:
@ -134,7 +141,7 @@ packages:
name: rxdart name: rxdart
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.24.0" version: "0.24.1"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -181,7 +188,7 @@ packages:
name: test_api name: test_api
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.14" version: "0.2.11"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description: