package com.miniabs; import java.io.File; import android.content.Context; import android.net.Uri; import android.os.Handler; import android.os.Looper; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.DefaultLoadControl; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.PlaybackException; import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.RenderersFactory; import com.google.android.exoplayer2.Tracks; import com.google.android.exoplayer2.audio.AudioAttributes; import com.google.android.exoplayer2.audio.AudioRendererEventListener; import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer; import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; import com.google.android.exoplayer2.metadata.MetadataOutput; import com.google.android.exoplayer2.source.ProgressiveMediaSource; import com.google.android.exoplayer2.text.TextOutput; import com.google.android.exoplayer2.upstream.DefaultDataSource; import com.google.android.exoplayer2.upstream.DefaultHttpDataSource; import com.google.android.exoplayer2.video.VideoRendererEventListener; /** * ExoPlayer 2.18 (Android 4.1+ build): audio only, decoded with Android's MediaCodec. Over MediaPlayer it brings * playback speed and skipping silences (its own audio processing, so it works on Android 4.1), a buffer of minutes, * and errors that say what failed. Audio focus, unplugging and the wake/Wi-Fi locks stay with PlayerService. */ final class ExoEngine implements Engine { private final ExoPlayer player; private Listener listener; private boolean preparedSent; private boolean failed; ExoEngine(final Context c) { RenderersFactory audioOnly = new RenderersFactory() { public Renderer[] createRenderers(Handler h, VideoRendererEventListener v, AudioRendererEventListener a, TextOutput t, MetadataOutput m) { return new Renderer[] {new MediaCodecAudioRenderer(c, MediaCodecSelector.DEFAULT, h, a)}; } }; DefaultHttpDataSource.Factory http = new DefaultHttpDataSource.Factory() .setUserAgent("Podlite").setConnectTimeoutMs(15000).setReadTimeoutMs(30000) .setAllowCrossProtocolRedirects(true); ProgressiveMediaSource.Factory sources = new ProgressiveMediaSource.Factory(new DefaultDataSource.Factory(c, http), new DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true)); player = new ExoPlayer.Builder(c, audioOnly, sources) // up to 3 minutes ahead in memory: rides out Wi-Fi gaps that MediaPlayer's seconds of buffer couldn't .setLoadControl(new DefaultLoadControl.Builder() .setBufferDurationsMs(60000, 180000, 1500, 3000) .setPrioritizeTimeOverSizeThresholds(true) .build()) .setAudioAttributes(new AudioAttributes.Builder() .setUsage(C.USAGE_MEDIA).setContentType(C.AUDIO_CONTENT_TYPE_SPEECH).build(), false) .setHandleAudioBecomingNoisy(false) .setWakeMode(C.WAKE_MODE_LOCAL) .setLooper(Looper.getMainLooper()) .build(); player.addListener(new Player.Listener() { public void onPlaybackStateChanged(int state) { if (listener == null || failed) return; if (state == Player.STATE_READY && !preparedSent) { preparedSent = true; listener.onPrepared(ExoEngine.this); } else if (state == Player.STATE_ENDED) { listener.onCompletion(ExoEngine.this); } } public void onPlayerError(PlaybackException e) { if (listener == null || failed) return; failed = true; int code = e.errorCode; // 4xxx: decoding; 3003: a container this player can't read. Android's MediaPlayer may manage. boolean decoder = (code >= 4000 && code < 5000) || code == PlaybackException.ERROR_CODE_PARSING_CONTAINER_UNSUPPORTED; Throwable cause = e.getCause(); String why = cause != null && cause.getMessage() != null ? cause.getMessage() : e.getMessage(); listener.onError(ExoEngine.this, "Erro de reprodución: " + e.getErrorCodeName() + (why != null ? " (" + why + ")" : ""), decoder); } public void onTracksChanged(Tracks tracks) { // ExoPlayer plays silence through audio it can't decode instead of failing: fail, so MediaPlayer can try if (listener == null || failed || tracks.isEmpty() || tracks.isTypeSupported(C.TRACK_TYPE_AUDIO, true)) return; failed = true; listener.onError(ExoEngine.this, "Este teléfono non pode descodificar este audio.", true); } }); } public void setListener(Listener l) { listener = l; } public void setSource(File file, String url) { player.setMediaItem(MediaItem.fromUri(file != null ? Uri.fromFile(file) : Uri.parse(url))); } public void prepare() { player.prepare(); } public void start() { player.setPlayWhenReady(true); } public void pause() { player.setPlayWhenReady(false); } public void seekTo(int ms) { player.seekTo(ms); } public int position() { return (int) player.getCurrentPosition(); } public int duration() { long d = player.getDuration(); return d == C.TIME_UNSET || d < 0 ? 0 : (int) d; } public int bufferedPercent() { return player.getBufferedPercentage(); } public void setVolume(float v) { player.setVolume(v); } public void setSpeed(float speed, boolean skipSilence) { player.setPlaybackParameters(new PlaybackParameters(speed)); player.setSkipSilenceEnabled(skipSilence); } public void release() { listener = null; player.release(); } }