Skip to content

Commit

Permalink
[video_player_android] Migrate ExoPlayer to ExoPlayer-Media3 1.3.1 (f…
Browse files Browse the repository at this point in the history
  • Loading branch information
emakar authored and arc-yong committed Jun 14, 2024
1 parent 7f5b75e commit 4400fcb
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 116 deletions.
4 changes: 4 additions & 0 deletions packages/video_player/video_player_android/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.5.0

* Migrates ExoPlayer to Media3-ExoPlayer 1.3.1.

## 2.4.17

* Revert Impeller support.
Expand Down
10 changes: 5 additions & 5 deletions packages/video_player/video_player_android/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@ android {
}

dependencies {
def exoplayer_version = "2.18.7"
implementation "com.google.android.exoplayer:exoplayer-core:${exoplayer_version}"
implementation "com.google.android.exoplayer:exoplayer-hls:${exoplayer_version}"
implementation "com.google.android.exoplayer:exoplayer-dash:${exoplayer_version}"
implementation "com.google.android.exoplayer:exoplayer-smoothstreaming:${exoplayer_version}"
def exoplayer_version = "1.3.1"
implementation "androidx.media3:media3-exoplayer:${exoplayer_version}"
implementation "androidx.media3:media3-exoplayer-hls:${exoplayer_version}"
implementation "androidx.media3:media3-exoplayer-dash:${exoplayer_version}"
implementation "androidx.media3:media3-exoplayer-smoothstreaming:${exoplayer_version}"
testImplementation 'junit:junit:4.13.2'
testImplementation 'androidx.test:core:1.3.0'
testImplementation 'org.mockito:mockito-inline:5.0.0'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,30 @@

package io.flutter.plugins.videoplayer;

import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL;
import static com.google.android.exoplayer2.Player.REPEAT_MODE_OFF;
import static androidx.media3.common.Player.REPEAT_MODE_ALL;
import static androidx.media3.common.Player.REPEAT_MODE_OFF;

import android.content.Context;
import android.net.Uri;
import android.view.Surface;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.annotation.VisibleForTesting;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Format;
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.Player.Listener;
import com.google.android.exoplayer2.audio.AudioAttributes;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.source.dash.DashMediaSource;
import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource;
import com.google.android.exoplayer2.source.hls.HlsMediaSource;
import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource;
import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSource;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
import com.google.android.exoplayer2.util.Util;
import androidx.media3.common.AudioAttributes;
import androidx.media3.common.C;
import androidx.media3.common.MediaItem;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.PlaybackException;
import androidx.media3.common.PlaybackParameters;
import androidx.media3.common.Player;
import androidx.media3.common.Player.Listener;
import androidx.media3.common.VideoSize;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.datasource.DataSource;
import androidx.media3.datasource.DefaultDataSource;
import androidx.media3.datasource.DefaultHttpDataSource;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
import io.flutter.plugin.common.EventChannel;
import io.flutter.view.TextureRegistry;
import java.util.Arrays;
Expand Down Expand Up @@ -62,7 +58,7 @@ final class VideoPlayer {

private final VideoPlayerOptions options;

private DefaultHttpDataSource.Factory httpDataSourceFactory = new DefaultHttpDataSource.Factory();
private final DefaultHttpDataSource.Factory httpDataSourceFactory;

VideoPlayer(
Context context,
Expand All @@ -76,16 +72,18 @@ final class VideoPlayer {
this.textureEntry = textureEntry;
this.options = options;

ExoPlayer exoPlayer = new ExoPlayer.Builder(context).build();
Uri uri = Uri.parse(dataSource);
MediaItem mediaItem =
new MediaItem.Builder()
.setUri(dataSource)
.setMimeType(mimeFromFormatHint(formatHint))
.build();

buildHttpDataSourceFactory(httpHeaders);
DataSource.Factory dataSourceFactory =
new DefaultDataSource.Factory(context, httpDataSourceFactory);
httpDataSourceFactory = new DefaultHttpDataSource.Factory();
configureHttpDataSourceFactory(httpHeaders);

MediaSource mediaSource = buildMediaSource(uri, dataSourceFactory, formatHint);
ExoPlayer exoPlayer = buildExoPlayer(context, httpDataSourceFactory);

exoPlayer.setMediaSource(mediaSource);
exoPlayer.setMediaItem(mediaItem);
exoPlayer.prepare();

setUpVideoPlayer(exoPlayer, new QueuingEventSink());
Expand All @@ -109,64 +107,15 @@ final class VideoPlayer {
}

@VisibleForTesting
public void buildHttpDataSourceFactory(@NonNull Map<String, String> httpHeaders) {
public void configureHttpDataSourceFactory(@NonNull Map<String, String> httpHeaders) {
final boolean httpHeadersNotEmpty = !httpHeaders.isEmpty();
final String userAgent =
httpHeadersNotEmpty && httpHeaders.containsKey(USER_AGENT)
? httpHeaders.get(USER_AGENT)
: "ExoPlayer";

httpDataSourceFactory.setUserAgent(userAgent).setAllowCrossProtocolRedirects(true);

if (httpHeadersNotEmpty) {
httpDataSourceFactory.setDefaultRequestProperties(httpHeaders);
}
}

private MediaSource buildMediaSource(
Uri uri, DataSource.Factory mediaDataSourceFactory, String formatHint) {
int type;
if (formatHint == null) {
type = Util.inferContentType(uri);
} else {
switch (formatHint) {
case FORMAT_SS:
type = C.CONTENT_TYPE_SS;
break;
case FORMAT_DASH:
type = C.CONTENT_TYPE_DASH;
break;
case FORMAT_HLS:
type = C.CONTENT_TYPE_HLS;
break;
case FORMAT_OTHER:
type = C.CONTENT_TYPE_OTHER;
break;
default:
type = -1;
break;
}
}
switch (type) {
case C.CONTENT_TYPE_SS:
return new SsMediaSource.Factory(
new DefaultSsChunkSource.Factory(mediaDataSourceFactory), mediaDataSourceFactory)
.createMediaSource(MediaItem.fromUri(uri));
case C.CONTENT_TYPE_DASH:
return new DashMediaSource.Factory(
new DefaultDashChunkSource.Factory(mediaDataSourceFactory), mediaDataSourceFactory)
.createMediaSource(MediaItem.fromUri(uri));
case C.CONTENT_TYPE_HLS:
return new HlsMediaSource.Factory(mediaDataSourceFactory)
.createMediaSource(MediaItem.fromUri(uri));
case C.CONTENT_TYPE_OTHER:
return new ProgressiveMediaSource.Factory(mediaDataSourceFactory)
.createMediaSource(MediaItem.fromUri(uri));
default:
{
throw new IllegalStateException("Unsupported type: " + type);
}
}
unstableUpdateDataSourceFactory(
httpDataSourceFactory, httpHeaders, userAgent, httpHeadersNotEmpty);
}

private void setUpVideoPlayer(ExoPlayer exoPlayer, QueuingEventSink eventSink) {
Expand Down Expand Up @@ -304,15 +253,15 @@ void sendInitialized() {
event.put("event", "initialized");
event.put("duration", exoPlayer.getDuration());

if (exoPlayer.getVideoFormat() != null) {
Format videoFormat = exoPlayer.getVideoFormat();
int width = videoFormat.width;
int height = videoFormat.height;
int rotationDegrees = videoFormat.rotationDegrees;
VideoSize videoSize = exoPlayer.getVideoSize();
int width = videoSize.width;
int height = videoSize.height;
if (width != 0 && height != 0) {
int rotationDegrees = videoSize.unappliedRotationDegrees;
// Switch the width/height if video was taken in portrait mode
if (rotationDegrees == 90 || rotationDegrees == 270) {
width = exoPlayer.getVideoFormat().height;
height = exoPlayer.getVideoFormat().width;
width = videoSize.height;
height = videoSize.width;
}
event.put("width", width);
event.put("height", height);
Expand Down Expand Up @@ -343,4 +292,46 @@ void dispose() {
exoPlayer.release();
}
}

@NonNull
private static ExoPlayer buildExoPlayer(
Context context, DataSource.Factory baseDataSourceFactory) {
DataSource.Factory dataSourceFactory =
new DefaultDataSource.Factory(context, baseDataSourceFactory);
DefaultMediaSourceFactory mediaSourceFactory =
new DefaultMediaSourceFactory(context).setDataSourceFactory(dataSourceFactory);
return new ExoPlayer.Builder(context).setMediaSourceFactory(mediaSourceFactory).build();
}

@Nullable
private static String mimeFromFormatHint(@Nullable String formatHint) {
if (formatHint == null) {
return null;
}
switch (formatHint) {
case FORMAT_SS:
return MimeTypes.APPLICATION_SS;
case FORMAT_DASH:
return MimeTypes.APPLICATION_MPD;
case FORMAT_HLS:
return MimeTypes.APPLICATION_M3U8;
case FORMAT_OTHER:
default:
return null;
}
}

// TODO: migrate to stable API, see https://github.com/flutter/flutter/issues/147039
@OptIn(markerClass = UnstableApi.class)
private static void unstableUpdateDataSourceFactory(
DefaultHttpDataSource.Factory factory,
@NonNull Map<String, String> httpHeaders,
String userAgent,
boolean httpHeadersNotEmpty) {
factory.setUserAgent(userAgent).setAllowCrossProtocolRedirects(true);

if (httpHeadersNotEmpty) {
factory.setDefaultRequestProperties(httpHeaders);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@
import static org.mockito.Mockito.times;

import android.graphics.SurfaceTexture;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.PlaybackException;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
import androidx.media3.common.PlaybackException;
import androidx.media3.common.Player;
import androidx.media3.common.VideoSize;
import androidx.media3.datasource.DefaultHttpDataSource;
import androidx.media3.exoplayer.ExoPlayer;
import io.flutter.plugin.common.EventChannel;
import io.flutter.view.TextureRegistry;
import java.util.HashMap;
Expand Down Expand Up @@ -71,7 +71,7 @@ public void videoPlayer_buildsHttpDataSourceFactoryProperlyWhenHttpHeadersNull()
fakeEventSink,
httpDataSourceFactorySpy);

videoPlayer.buildHttpDataSourceFactory(new HashMap<>());
videoPlayer.configureHttpDataSourceFactory(new HashMap<>());

verify(httpDataSourceFactorySpy).setUserAgent("ExoPlayer");
verify(httpDataSourceFactorySpy).setAllowCrossProtocolRedirects(true);
Expand All @@ -97,7 +97,7 @@ public void videoPlayer_buildsHttpDataSourceFactoryProperlyWhenHttpHeadersNull()
}
};

videoPlayer.buildHttpDataSourceFactory(httpHeaders);
videoPlayer.configureHttpDataSourceFactory(httpHeaders);

verify(httpDataSourceFactorySpy).setUserAgent("userAgent");
verify(httpDataSourceFactorySpy).setAllowCrossProtocolRedirects(true);
Expand All @@ -122,7 +122,7 @@ public void videoPlayer_buildsHttpDataSourceFactoryProperlyWhenHttpHeadersNull()
}
};

videoPlayer.buildHttpDataSourceFactory(httpHeaders);
videoPlayer.configureHttpDataSourceFactory(httpHeaders);

verify(httpDataSourceFactorySpy).setUserAgent("ExoPlayer");
verify(httpDataSourceFactorySpy).setAllowCrossProtocolRedirects(true);
Expand All @@ -139,10 +139,9 @@ public void sendInitializedSendsExpectedEvent_90RotationDegrees() {
fakeVideoPlayerOptions,
fakeEventSink,
httpDataSourceFactorySpy);
Format testFormat =
new Format.Builder().setWidth(100).setHeight(200).setRotationDegrees(90).build();
VideoSize testVideoSize = new VideoSize(100, 200, 90, 1f);

when(fakeExoPlayer.getVideoFormat()).thenReturn(testFormat);
when(fakeExoPlayer.getVideoSize()).thenReturn(testVideoSize);
when(fakeExoPlayer.getDuration()).thenReturn(10L);

videoPlayer.isInitialized = true;
Expand All @@ -168,10 +167,9 @@ public void sendInitializedSendsExpectedEvent_270RotationDegrees() {
fakeVideoPlayerOptions,
fakeEventSink,
httpDataSourceFactorySpy);
Format testFormat =
new Format.Builder().setWidth(100).setHeight(200).setRotationDegrees(270).build();
VideoSize testVideoSize = new VideoSize(100, 200, 270, 1f);

when(fakeExoPlayer.getVideoFormat()).thenReturn(testFormat);
when(fakeExoPlayer.getVideoSize()).thenReturn(testVideoSize);
when(fakeExoPlayer.getDuration()).thenReturn(10L);

videoPlayer.isInitialized = true;
Expand All @@ -197,10 +195,9 @@ public void sendInitializedSendsExpectedEvent_0RotationDegrees() {
fakeVideoPlayerOptions,
fakeEventSink,
httpDataSourceFactorySpy);
Format testFormat =
new Format.Builder().setWidth(100).setHeight(200).setRotationDegrees(0).build();
VideoSize testVideoSize = new VideoSize(100, 200, 0, 1f);

when(fakeExoPlayer.getVideoFormat()).thenReturn(testFormat);
when(fakeExoPlayer.getVideoSize()).thenReturn(testVideoSize);
when(fakeExoPlayer.getDuration()).thenReturn(10L);

videoPlayer.isInitialized = true;
Expand All @@ -226,10 +223,9 @@ public void sendInitializedSendsExpectedEvent_180RotationDegrees() {
fakeVideoPlayerOptions,
fakeEventSink,
httpDataSourceFactorySpy);
Format testFormat =
new Format.Builder().setWidth(100).setHeight(200).setRotationDegrees(180).build();
VideoSize testVideoSize = new VideoSize(100, 200, 180, 1f);

when(fakeExoPlayer.getVideoFormat()).thenReturn(testFormat);
when(fakeExoPlayer.getVideoSize()).thenReturn(testVideoSize);
when(fakeExoPlayer.getDuration()).thenReturn(10L);

videoPlayer.isInitialized = true;
Expand Down
2 changes: 1 addition & 1 deletion packages/video_player/video_player_android/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: video_player_android
description: Android implementation of the video_player plugin.
repository: https://github.com/flutter/packages/tree/main/packages/video_player/video_player_android
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22
version: 2.4.17
version: 2.5.0

environment:
sdk: ^3.4.0
Expand Down

0 comments on commit 4400fcb

Please sign in to comment.