Android: Support Android Embedding V2 (#109)

* Bump AGP & Gradle
* Migrate the project to Android Embedding to V2
This commit is contained in:
Sebastian Roth 2020-06-08 12:20:14 +01:00 committed by GitHub
parent 4dee0906db
commit 8f146e559a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 158 additions and 74 deletions

View File

@ -8,7 +8,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.5.0' classpath 'com.android.tools.build:gradle:3.6.3'
} }
} }

View File

@ -3,7 +3,6 @@ package com.ryanheise.just_audio;
import android.content.Context; import android.content.Context;
import android.net.Uri; 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.PlaybackParameters; import com.google.android.exoplayer2.PlaybackParameters;
@ -26,31 +25,28 @@ import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
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.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.BinaryMessenger;
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;
import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler; 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 java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
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";
private final Registrar registrar;
private final Context context; private final Context context;
private final MethodChannel methodChannel; private final MethodChannel methodChannel;
private final EventChannel eventChannel; private final EventChannel eventChannel;
private EventSink eventSink; private EventSink eventSink;
private final String id;
private volatile PlaybackState state; private volatile PlaybackState state;
private long updateTime; private long updateTime;
private long updatePosition; private long updatePosition;
@ -70,11 +66,15 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Met
private IcyInfo icyInfo; private IcyInfo icyInfo;
private IcyHeaders icyHeaders; private IcyHeaders icyHeaders;
private final SimpleExoPlayer player; private SimpleExoPlayer player;
private final Handler handler = new Handler(); private final Handler handler = new Handler();
private final Runnable bufferWatcher = new Runnable() { private final Runnable bufferWatcher = new Runnable() {
@Override @Override
public void run() { public void run() {
if (player == null) {
return;
}
long newBufferedPosition = player.getBufferedPosition(); long newBufferedPosition = player.getBufferedPosition();
if (newBufferedPosition != bufferedPosition) { if (newBufferedPosition != bufferedPosition) {
bufferedPosition = newBufferedPosition; bufferedPosition = newBufferedPosition;
@ -92,13 +92,15 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Met
} }
}; };
public AudioPlayer(final Registrar registrar, final String id) { private final Runnable onDispose;
this.registrar = registrar;
this.context = registrar.activeContext(); public AudioPlayer(final Context applicationContext, final BinaryMessenger messenger,
this.id = id; final String id, final Runnable onDispose) {
methodChannel = new MethodChannel(registrar.messenger(), "com.ryanheise.just_audio.methods." + id); this.context = applicationContext;
this.onDispose = onDispose;
methodChannel = new MethodChannel(messenger, "com.ryanheise.just_audio.methods." + id);
methodChannel.setMethodCallHandler(this); methodChannel.setMethodCallHandler(this);
eventChannel = new EventChannel(registrar.messenger(), "com.ryanheise.just_audio.events." + id); eventChannel = new EventChannel(messenger, "com.ryanheise.just_audio.events." + id);
eventChannel.setStreamHandler(new EventChannel.StreamHandler() { eventChannel.setStreamHandler(new EventChannel.StreamHandler() {
@Override @Override
public void onListen(final Object arguments, final EventSink eventSink) { public void onListen(final Object arguments, final EventSink eventSink) {
@ -111,10 +113,6 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Met
} }
}); });
state = PlaybackState.none; state = PlaybackState.none;
player = new SimpleExoPlayer.Builder(context).build();
player.addMetadataOutput(this);
player.addListener(this);
} }
private void startWatchingBuffer() { private void startWatchingBuffer() {
@ -227,22 +225,24 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Met
@Override @Override
public void onMethodCall(final MethodCall call, final Result result) { public void onMethodCall(final MethodCall call, final Result result) {
final List<?> args = (List<?>)call.arguments; ensurePlayerInitialized();
final List<?> args = (List<?>) call.arguments;
try { try {
switch (call.method) { switch (call.method) {
case "setUrl": case "setUrl":
setUrl((String)args.get(0), result); setUrl((String) args.get(0), result);
break; break;
case "setClip": case "setClip":
Object start = args.get(0); Object start = args.get(0);
if (start != null && start instanceof Integer) { if (start != null && start instanceof Integer) {
start = new Long((Integer)start); start = new Long((Integer) start);
} }
Object end = args.get(1); Object end = args.get(1);
if (end != null && end instanceof Integer) { if (end != null && end instanceof Integer) {
end = new Long((Integer)end); end = new Long((Integer) end);
} }
setClip((Long)start, (Long)end, result); setClip((Long) start, (Long) end, result);
break; break;
case "play": case "play":
play(); play();
@ -256,11 +256,11 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Met
stop(result); stop(result);
break; break;
case "setVolume": case "setVolume":
setVolume((float)((double)((Double)args.get(0)))); setVolume((float) ((double) ((Double) args.get(0))));
result.success(null); result.success(null);
break; break;
case "setSpeed": case "setSpeed":
setSpeed((float)((double)((Double)args.get(0)))); setSpeed((float) ((double) ((Double) args.get(0))));
result.success(null); result.success(null);
break; break;
case "setAutomaticallyWaitsToMinimizeStalling": case "setAutomaticallyWaitsToMinimizeStalling":
@ -270,14 +270,15 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Met
Object position = args.get(0); Object position = args.get(0);
long position2; long position2;
if (position instanceof Integer) { if (position instanceof Integer) {
position2 = (Integer)position; position2 = (Integer) position;
} else { } else {
position2 = (Long)position; position2 = (Long) position;
} }
seek(position2 == -2 ? C.TIME_UNSET : position2, result); seek(position2 == -2 ? C.TIME_UNSET : position2, result);
break; break;
case "dispose": case "dispose":
dispose(); dispose();
onDispose.run();
result.success(null); result.success(null);
break; break;
default: default:
@ -293,6 +294,14 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Met
} }
} }
private void ensurePlayerInitialized() {
if (player == null) {
player = new SimpleExoPlayer.Builder(context).build();
player.addMetadataOutput(this);
player.addListener(this);
}
}
private void broadcastPlaybackEvent() { private void broadcastPlaybackEvent() {
final ArrayList<Object> event = new ArrayList<Object>(); final ArrayList<Object> event = new ArrayList<Object>();
event.add(state.ordinal()); event.add(state.ordinal());
@ -382,7 +391,8 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Met
DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS, DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS,
true true
); );
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(context, httpDataSourceFactory); DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(context,
httpDataSourceFactory);
Uri uri = Uri.parse(url); Uri uri = Uri.parse(url);
String extension = getLowerCaseExtension(uri); String extension = getLowerCaseExtension(uri);
if (extension.equals("mpd")) { if (extension.equals("mpd")) {
@ -435,7 +445,8 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Met
player.setPlayWhenReady(true); player.setPlayWhenReady(true);
break; break;
default: default:
throw new IllegalStateException("Cannot call play from connecting/none states (" + state + ")"); throw new IllegalStateException(
"Cannot call play from connecting/none states (" + state + ")");
} }
} }
@ -448,7 +459,8 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Met
transition(PlaybackState.paused); transition(PlaybackState.paused);
break; break;
default: default:
throw new IllegalStateException("Can call pause only from playing and buffering states (" + state + ")"); throw new IllegalStateException(
"Can call pause only from playing and buffering states (" + state + ")");
} }
} }
@ -500,10 +512,13 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Met
} }
public void dispose() { public void dispose() {
if (player != null) {
player.release(); player.release();
player = null;
buffering = false; buffering = false;
transition(PlaybackState.none); transition(PlaybackState.none);
} }
}
private void abortSeek() { private void abortSeek() {
if (seekResult != null) { if (seekResult != null) {

View File

@ -1,41 +1,57 @@
package com.ryanheise.just_audio; package com.ryanheise.just_audio;
import io.flutter.plugin.common.MethodCall; import android.content.Context;
import androidx.annotation.NonNull;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.common.PluginRegistry.Registrar; import io.flutter.plugin.common.PluginRegistry.Registrar;
import java.util.List;
/** JustAudioPlugin */ /**
public class JustAudioPlugin implements MethodCallHandler { * JustAudioPlugin
/** Plugin registration. */ */
public static void registerWith(Registrar registrar) { public class JustAudioPlugin implements FlutterPlugin {
final MethodChannel channel = new MethodChannel(registrar.messenger(), "com.ryanheise.just_audio.methods");
channel.setMethodCallHandler(new JustAudioPlugin(registrar)); private MethodChannel channel;
private MethodCallHandlerImpl methodCallHandler;
public JustAudioPlugin() {
} }
private Registrar registrar; /**
* Plugin registration.
public JustAudioPlugin(Registrar registrar) { */
this.registrar = registrar; public static void registerWith(Registrar registrar) {
final JustAudioPlugin plugin = new JustAudioPlugin();
plugin.startListening(registrar.context(), registrar.messenger());
registrar.addViewDestroyListener(
view -> {
plugin.stopListening();
return false;
});
} }
@Override @Override
public void onMethodCall(MethodCall call, Result result) { public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
switch (call.method) { startListening(binding.getApplicationContext(), binding.getBinaryMessenger());
case "init":
final List<?> args = (List<?>)call.arguments;
String id = (String)args.get(0);
new AudioPlayer(registrar, id);
result.success(null);
break;
case "setIosCategory":
result.success(null);
break;
default:
result.notImplemented();
break;
} }
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
stopListening();
}
private void startListening(Context applicationContext, BinaryMessenger messenger) {
methodCallHandler = new MethodCallHandlerImpl(applicationContext, messenger);
channel = new MethodChannel(messenger, "com.ryanheise.just_audio.methods");
channel.setMethodCallHandler(methodCallHandler);
}
private void stopListening() {
methodCallHandler.dispose();
methodCallHandler = null;
channel.setMethodCallHandler(null);
} }
} }

View File

@ -0,0 +1,53 @@
package com.ryanheise.just_audio;
import android.content.Context;
import androidx.annotation.NonNull;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MethodCallHandlerImpl implements MethodCallHandler {
private final Context applicationContext;
private final BinaryMessenger messenger;
private final Map<String, AudioPlayer> players = new HashMap<>();
public MethodCallHandlerImpl(Context applicationContext,
BinaryMessenger messenger) {
this.applicationContext = applicationContext;
this.messenger = messenger;
}
@Override
public void onMethodCall(MethodCall call, @NonNull Result result) {
switch (call.method) {
case "init":
final List<String> ids = call.arguments();
String id = ids.get(0);
players.put(id, new AudioPlayer(applicationContext, messenger, id,
() -> players.remove(id)
));
result.success(null);
break;
case "setIosCategory":
result.success(null);
break;
default:
result.notImplemented();
break;
}
}
void dispose() {
for (AudioPlayer player : players.values()) {
player.dispose();
}
players.clear();
}
}

View File

@ -5,7 +5,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.5.0' classpath 'com.android.tools.build:gradle:3.6.3'
} }
} }

View File

@ -1,6 +1,6 @@
#Fri Jun 23 08:50:38 CEST 2017 #Sun Jun 07 15:20:36 BST 2020
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip

View File

@ -451,7 +451,7 @@ class AudioPlayer {
/// * [AudioPlaybackState.none] /// * [AudioPlaybackState.none]
/// * [AudioPlaybackState.connecting] /// * [AudioPlaybackState.connecting]
Future<void> dispose() async { Future<void> dispose() async {
await _invokeMethod('dispose'); await _invokeMethod('dispose', [_id]);
if (_cacheFile?.existsSync() == true) { if (_cacheFile?.existsSync() == true) {
_cacheFile?.deleteSync(); _cacheFile?.deleteSync();
} }