-
Notifications
You must be signed in to change notification settings - Fork 3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Final refactor of
video_player_android
before `SurfaceProducer#setC…
…allback`. (#6982) I'm working on re-landing #6456, this time without using the `ActivityAware` interface (see flutter/flutter#148417). As part of that work, I'll need to better control the `ExoPlayer` lifecycle and save/restore internal state. Follows the patterns of some of the previous PRs, i.e. - #6922 - #6908 The changes in this PR are _mostly_ tests, it was extremely difficult to just add more tests to the already very leaky `VideoPlayer` abstraction which had lots of `@VisibleForTesting` methods and other "holes" to observe state. This PR removes all of that, and adds test coverage where it was missing. Namely it: - Adds a new class, `VideoAsset`, that builds and configures the media that `ExoPlayer` uses. - Removes all "testing" state from `VidePlayer`, keeping it nearly immutable. - Added tests for most of the classes I've added since, which were mostly missing. That being said, this is a large change. I'm happy to sit down with either of you and walk through it. --- Opening as a draft for the moment, since there is a pubspec change needing I want to handle first.
- Loading branch information
1 parent
eb0e54a
commit 1612774
Showing
12 changed files
with
859 additions
and
404 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
28 changes: 28 additions & 0 deletions
28
..._player_android/android/src/main/java/io/flutter/plugins/videoplayer/LocalVideoAsset.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
// Copyright 2013 The Flutter Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
package io.flutter.plugins.videoplayer; | ||
|
||
import android.content.Context; | ||
import androidx.annotation.NonNull; | ||
import androidx.media3.common.MediaItem; | ||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory; | ||
import androidx.media3.exoplayer.source.MediaSource; | ||
|
||
final class LocalVideoAsset extends VideoAsset { | ||
LocalVideoAsset(@NonNull String assetUrl) { | ||
super(assetUrl); | ||
} | ||
|
||
@NonNull | ||
@Override | ||
MediaItem getMediaItem() { | ||
return new MediaItem.Builder().setUri(assetUrl).build(); | ||
} | ||
|
||
@Override | ||
MediaSource.Factory getMediaSourceFactory(Context context) { | ||
return new DefaultMediaSourceFactory(context); | ||
} | ||
} |
97 changes: 97 additions & 0 deletions
97
...player_android/android/src/main/java/io/flutter/plugins/videoplayer/RemoteVideoAsset.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
// Copyright 2013 The Flutter Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
package io.flutter.plugins.videoplayer; | ||
|
||
import android.content.Context; | ||
import androidx.annotation.NonNull; | ||
import androidx.annotation.Nullable; | ||
import androidx.annotation.OptIn; | ||
import androidx.annotation.VisibleForTesting; | ||
import androidx.media3.common.MediaItem; | ||
import androidx.media3.common.MimeTypes; | ||
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.source.DefaultMediaSourceFactory; | ||
import androidx.media3.exoplayer.source.MediaSource; | ||
import java.util.Map; | ||
|
||
final class RemoteVideoAsset extends VideoAsset { | ||
private static final String DEFAULT_USER_AGENT = "ExoPlayer"; | ||
private static final String HEADER_USER_AGENT = "User-Agent"; | ||
|
||
@NonNull private final StreamingFormat streamingFormat; | ||
@NonNull private final Map<String, String> httpHeaders; | ||
|
||
RemoteVideoAsset( | ||
@Nullable String assetUrl, | ||
@NonNull StreamingFormat streamingFormat, | ||
@NonNull Map<String, String> httpHeaders) { | ||
super(assetUrl); | ||
this.streamingFormat = streamingFormat; | ||
this.httpHeaders = httpHeaders; | ||
} | ||
|
||
@NonNull | ||
@Override | ||
MediaItem getMediaItem() { | ||
MediaItem.Builder builder = new MediaItem.Builder().setUri(assetUrl); | ||
String mimeType = null; | ||
switch (streamingFormat) { | ||
case SMOOTH: | ||
mimeType = MimeTypes.APPLICATION_SS; | ||
break; | ||
case DYNAMIC_ADAPTIVE: | ||
mimeType = MimeTypes.APPLICATION_MPD; | ||
break; | ||
case HTTP_LIVE: | ||
mimeType = MimeTypes.APPLICATION_M3U8; | ||
break; | ||
} | ||
if (mimeType != null) { | ||
builder.setMimeType(mimeType); | ||
} | ||
return builder.build(); | ||
} | ||
|
||
@Override | ||
MediaSource.Factory getMediaSourceFactory(Context context) { | ||
return getMediaSourceFactory(context, new DefaultHttpDataSource.Factory()); | ||
} | ||
|
||
/** | ||
* Returns a configured media source factory, starting at the provided factory. | ||
* | ||
* <p>This method is provided for ease of testing without making real HTTP calls. | ||
* | ||
* @param context application context. | ||
* @param initialFactory initial factory, to be configured. | ||
* @return configured factory, or {@code null} if not needed for this asset type. | ||
*/ | ||
@VisibleForTesting | ||
MediaSource.Factory getMediaSourceFactory( | ||
Context context, DefaultHttpDataSource.Factory initialFactory) { | ||
String userAgent = DEFAULT_USER_AGENT; | ||
if (!httpHeaders.isEmpty() && httpHeaders.containsKey(HEADER_USER_AGENT)) { | ||
userAgent = httpHeaders.get(HEADER_USER_AGENT); | ||
} | ||
unstableUpdateDataSourceFactory(initialFactory, httpHeaders, userAgent); | ||
DataSource.Factory dataSoruceFactory = new DefaultDataSource.Factory(context, initialFactory); | ||
return new DefaultMediaSourceFactory(context).setDataSourceFactory(dataSoruceFactory); | ||
} | ||
|
||
// TODO: Migrate to stable API, see https://github.com/flutter/flutter/issues/147039. | ||
@OptIn(markerClass = UnstableApi.class) | ||
private static void unstableUpdateDataSourceFactory( | ||
@NonNull DefaultHttpDataSource.Factory factory, | ||
@NonNull Map<String, String> httpHeaders, | ||
@Nullable String userAgent) { | ||
factory.setUserAgent(userAgent).setAllowCrossProtocolRedirects(true); | ||
if (!httpHeaders.isEmpty()) { | ||
factory.setDefaultRequestProperties(httpHeaders); | ||
} | ||
} | ||
} |
83 changes: 83 additions & 0 deletions
83
...video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoAsset.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
// Copyright 2013 The Flutter Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
package io.flutter.plugins.videoplayer; | ||
|
||
import android.content.Context; | ||
import androidx.annotation.NonNull; | ||
import androidx.annotation.Nullable; | ||
import androidx.media3.common.MediaItem; | ||
import androidx.media3.exoplayer.source.MediaSource; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
/** A video to be played by {@link VideoPlayer}. */ | ||
abstract class VideoAsset { | ||
/** | ||
* Returns an asset from a local {@code asset:///} URL, i.e. an on-device asset. | ||
* | ||
* @param assetUrl local asset, beginning in {@code asset:///}. | ||
* @return the asset. | ||
*/ | ||
@NonNull | ||
static VideoAsset fromAssetUrl(@NonNull String assetUrl) { | ||
if (!assetUrl.startsWith("asset:///")) { | ||
throw new IllegalArgumentException("assetUrl must start with 'asset:///'"); | ||
} | ||
return new LocalVideoAsset(assetUrl); | ||
} | ||
|
||
/** | ||
* Returns an asset from a remote URL. | ||
* | ||
* @param remoteUrl remote asset, i.e. typically beginning with {@code https://} or similar. | ||
* @param streamingFormat which streaming format, provided as a hint if able. | ||
* @param httpHeaders HTTP headers to set for a request. | ||
* @return the asset. | ||
*/ | ||
@NonNull | ||
static VideoAsset fromRemoteUrl( | ||
@Nullable String remoteUrl, | ||
@NonNull StreamingFormat streamingFormat, | ||
@NonNull Map<String, String> httpHeaders) { | ||
return new RemoteVideoAsset(remoteUrl, streamingFormat, new HashMap<>(httpHeaders)); | ||
} | ||
|
||
@Nullable protected final String assetUrl; | ||
|
||
protected VideoAsset(@Nullable String assetUrl) { | ||
this.assetUrl = assetUrl; | ||
} | ||
|
||
/** | ||
* Returns the configured media item to be played. | ||
* | ||
* @return media item. | ||
*/ | ||
@NonNull | ||
abstract MediaItem getMediaItem(); | ||
|
||
/** | ||
* Returns the configured media source factory, if needed for this asset type. | ||
* | ||
* @param context application context. | ||
* @return configured factory, or {@code null} if not needed for this asset type. | ||
*/ | ||
abstract MediaSource.Factory getMediaSourceFactory(Context context); | ||
|
||
/** Streaming formats that can be provided to the video player as a hint. */ | ||
enum StreamingFormat { | ||
/** Default, if the format is either not known or not another valid format. */ | ||
UNKNOWN, | ||
|
||
/** Smooth Streaming. */ | ||
SMOOTH, | ||
|
||
/** MPEG-DASH (Dynamic Adaptive over HTTP). */ | ||
DYNAMIC_ADAPTIVE, | ||
|
||
/** HTTP Live Streaming (HLS). */ | ||
HTTP_LIVE | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.