@capgo/capacitor-video-player 8.1.6 → 8.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -0
- package/android/build.gradle +1 -1
- package/android/src/main/java/com/capgo/videoplayer/FullscreenExoPlayerFragment.java +286 -32
- package/android/src/main/java/com/capgo/videoplayer/VideoPlayerPlugin.java +1 -1
- package/ios/Sources/VideoPlayerPlugin/VideoPlayerPlugin.swift +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,6 +29,50 @@ npm install @capgo/capacitor-video-player
|
|
|
29
29
|
npx cap sync
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
## Supported Features
|
|
33
|
+
|
|
34
|
+
| Feature | iOS | Android | Web |
|
|
35
|
+
| ------------------------------ | --- | ------- | --- |
|
|
36
|
+
| Fullscreen native video player | ✅ | ✅ | ❌ |
|
|
37
|
+
| Embedded web video player | ❌ | ❌ | ✅ |
|
|
38
|
+
| Picture in Picture | ✅ | ✅ | ❌ |
|
|
39
|
+
| Subtitles | ✅ | ✅ | ✅ |
|
|
40
|
+
| DRM-protected playback | ✅ | ✅ | ❌ |
|
|
41
|
+
| Chromecast sender playback | ❌ | ✅ | ❌ |
|
|
42
|
+
|
|
43
|
+
## Chromecast (Android)
|
|
44
|
+
|
|
45
|
+
Chromecast is supported on Android in fullscreen mode. Enable it with the `chromecast` option. When a Cast session starts, the plugin loads the same media URL on the receiver, keeps the current playback position, and routes play, pause, seek, and timeline controls through the Cast session.
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
await VideoPlayer.initPlayer({
|
|
49
|
+
mode: 'fullscreen',
|
|
50
|
+
playerId: 'main-player',
|
|
51
|
+
url: 'https://example.com/video.m3u8',
|
|
52
|
+
title: 'Video title',
|
|
53
|
+
smallTitle: 'Optional subtitle',
|
|
54
|
+
artwork: 'https://example.com/artwork.jpg',
|
|
55
|
+
chromecast: true,
|
|
56
|
+
showControls: true,
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Chromecast Options
|
|
61
|
+
|
|
62
|
+
| Option | Type | Default | Description |
|
|
63
|
+
| ------------ | --------- | ------- | ---------------------------------------------------------- |
|
|
64
|
+
| `chromecast` | `boolean` | `true` | Shows the Android Cast button and enables sender playback. |
|
|
65
|
+
| `title` | `string` | `""` | Title shown in the sender controls and receiver metadata. |
|
|
66
|
+
| `smallTitle` | `string` | `""` | Subtitle shown in the sender controls and metadata. |
|
|
67
|
+
| `artwork` | `string` | `""` | Image URL used for Cast metadata and sender Cast artwork. |
|
|
68
|
+
|
|
69
|
+
### Requirements and Notes
|
|
70
|
+
|
|
71
|
+
- The media URL must be reachable by the Chromecast device, not only by the Android app.
|
|
72
|
+
- Cast support depends on the receiver app and device media support. MP4, HLS, DASH, and SmoothStreaming streams are mapped to Cast-compatible MIME types by the plugin.
|
|
73
|
+
- Widevine DRM metadata is forwarded to the Cast media item. DRM-protected streams may still require a receiver that supports your license server and DRM flow.
|
|
74
|
+
- Request headers used by the Android local player are not automatically available to the Chromecast receiver. Use public URLs, signed URLs, cookies supported by your receiver, or a custom receiver for secured media.
|
|
75
|
+
|
|
32
76
|
## API
|
|
33
77
|
|
|
34
78
|
<docgen-index>
|
package/android/build.gradle
CHANGED
|
@@ -65,7 +65,7 @@ dependencies {
|
|
|
65
65
|
implementation 'com.google.android.exoplayer:extension-mediasession:2.19.1'
|
|
66
66
|
implementation 'com.google.android.exoplayer:exoplayer:2.19.1'
|
|
67
67
|
implementation 'com.google.android.exoplayer:extension-cast:2.19.1'
|
|
68
|
-
implementation 'com.squareup.picasso:picasso:2.
|
|
68
|
+
implementation 'com.squareup.picasso:picasso:2.71828'
|
|
69
69
|
testImplementation "junit:junit:$junitVersion"
|
|
70
70
|
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
|
71
71
|
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
|
@@ -56,6 +56,8 @@ import com.google.android.exoplayer2.PlaybackParameters;
|
|
|
56
56
|
import com.google.android.exoplayer2.Player;
|
|
57
57
|
import com.google.android.exoplayer2.audio.AudioAttributes;
|
|
58
58
|
import com.google.android.exoplayer2.ext.cast.CastPlayer;
|
|
59
|
+
import com.google.android.exoplayer2.ext.cast.DefaultMediaItemConverter;
|
|
60
|
+
import com.google.android.exoplayer2.ext.cast.MediaItemConverter;
|
|
59
61
|
import com.google.android.exoplayer2.ext.cast.SessionAvailabilityListener;
|
|
60
62
|
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;
|
|
61
63
|
import com.google.android.exoplayer2.source.MediaSource;
|
|
@@ -79,16 +81,21 @@ import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
|
|
79
81
|
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
|
80
82
|
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
|
|
81
83
|
import com.google.android.exoplayer2.util.MimeTypes;
|
|
84
|
+
import com.google.android.gms.cast.MediaInfo;
|
|
85
|
+
import com.google.android.gms.cast.MediaQueueItem;
|
|
86
|
+
import com.google.android.gms.cast.MediaTrack;
|
|
82
87
|
import com.google.android.gms.cast.framework.CastButtonFactory;
|
|
83
88
|
import com.google.android.gms.cast.framework.CastContext;
|
|
84
89
|
import com.google.android.gms.cast.framework.CastState;
|
|
85
90
|
import com.google.android.gms.cast.framework.CastStateListener;
|
|
91
|
+
import com.google.android.gms.common.images.WebImage;
|
|
86
92
|
import com.google.android.gms.tasks.OnCompleteListener;
|
|
87
93
|
import com.google.android.gms.tasks.Task;
|
|
88
94
|
import java.io.IOException;
|
|
89
95
|
import java.io.InputStream;
|
|
90
96
|
import java.net.HttpURLConnection;
|
|
91
97
|
import java.net.URL;
|
|
98
|
+
import java.util.ArrayList;
|
|
92
99
|
import java.util.Arrays;
|
|
93
100
|
import java.util.HashMap;
|
|
94
101
|
import java.util.List;
|
|
@@ -97,6 +104,7 @@ import java.util.Map;
|
|
|
97
104
|
import java.util.concurrent.Executor;
|
|
98
105
|
import java.util.concurrent.Executors;
|
|
99
106
|
import org.json.JSONException;
|
|
107
|
+
import org.json.JSONObject;
|
|
100
108
|
|
|
101
109
|
public class FullscreenExoPlayerFragment extends Fragment {
|
|
102
110
|
|
|
@@ -188,7 +196,6 @@ public class FullscreenExoPlayerFragment extends Fragment {
|
|
|
188
196
|
private MediaRouteButton mediaRouteButton;
|
|
189
197
|
private CastContext castContext;
|
|
190
198
|
private CastPlayer castPlayer;
|
|
191
|
-
private MediaItem mediaItem;
|
|
192
199
|
private MediaRouter mRouter;
|
|
193
200
|
private MediaRouter.Callback mCallback = new EmptyCallback();
|
|
194
201
|
private MediaRouteSelector mSelector;
|
|
@@ -996,18 +1003,7 @@ public class FullscreenExoPlayerFragment extends Fragment {
|
|
|
996
1003
|
mediaSources[0] = mediaSource;
|
|
997
1004
|
String mimeType = getMimeType(sturi);
|
|
998
1005
|
|
|
999
|
-
|
|
1000
|
-
String languageLabel = Locale.forLanguageTag(language).getDisplayLanguage();
|
|
1001
|
-
MediaItem.SubtitleConfiguration subConfig = new MediaItem.SubtitleConfiguration.Builder(sturi)
|
|
1002
|
-
.setMimeType(mimeType)
|
|
1003
|
-
.setUri(sturi)
|
|
1004
|
-
.setId(subTitle)
|
|
1005
|
-
.setLabel(languageLabel)
|
|
1006
|
-
.setRoleFlags(C.ROLE_FLAG_CAPTION)
|
|
1007
|
-
.setSelectionFlags(C.SELECTION_FLAG_DEFAULT)
|
|
1008
|
-
.setLanguage(language)
|
|
1009
|
-
.build();
|
|
1010
|
-
|
|
1006
|
+
MediaItem.SubtitleConfiguration subConfig = buildSubtitleConfiguration(sturi, mimeType);
|
|
1011
1007
|
SingleSampleMediaSource subtitleSource = new SingleSampleMediaSource.Factory(dataSourceFactory).createMediaSource(
|
|
1012
1008
|
subConfig,
|
|
1013
1009
|
C.TIME_UNSET
|
|
@@ -1331,7 +1327,7 @@ public class FullscreenExoPlayerFragment extends Fragment {
|
|
|
1331
1327
|
public void onComplete(Task<CastContext> task) {
|
|
1332
1328
|
if (task.isSuccessful()) {
|
|
1333
1329
|
castContext = task.getResult();
|
|
1334
|
-
castPlayer = new CastPlayer(castContext);
|
|
1330
|
+
castPlayer = new CastPlayer(castContext, new SubtitleMediaItemConverter());
|
|
1335
1331
|
mRouter = MediaRouter.getInstance(context);
|
|
1336
1332
|
mSelector = new MediaRouteSelector.Builder()
|
|
1337
1333
|
.addControlCategories(
|
|
@@ -1355,38 +1351,37 @@ public class FullscreenExoPlayerFragment extends Fragment {
|
|
|
1355
1351
|
};
|
|
1356
1352
|
CastButtonFactory.setUpMediaRouteButton(context, mediaRouteButton);
|
|
1357
1353
|
|
|
1358
|
-
|
|
1359
|
-
if (artwork != "") {
|
|
1360
|
-
movieMetadata = new MediaMetadata.Builder()
|
|
1361
|
-
.setTitle(title)
|
|
1362
|
-
.setSubtitle(smallTitle)
|
|
1363
|
-
.setMediaType(MediaMetadata.MEDIA_TYPE_MOVIE)
|
|
1364
|
-
.setArtworkUri(Uri.parse(artwork))
|
|
1365
|
-
.build();
|
|
1354
|
+
if (hasArtwork()) {
|
|
1366
1355
|
new setCastImage().execute();
|
|
1367
|
-
} else {
|
|
1368
|
-
movieMetadata = new MediaMetadata.Builder().setTitle(title).setSubtitle(smallTitle).build();
|
|
1369
1356
|
}
|
|
1370
|
-
mediaItem = new MediaItem.Builder()
|
|
1371
|
-
.setUri(videoPath)
|
|
1372
|
-
.setMimeType(MimeTypes.VIDEO_UNKNOWN)
|
|
1373
|
-
.setMediaMetadata(movieMetadata)
|
|
1374
|
-
.build();
|
|
1375
1357
|
|
|
1376
1358
|
castPlayer.setSessionAvailabilityListener(
|
|
1377
1359
|
new SessionAvailabilityListener() {
|
|
1378
1360
|
@Override
|
|
1379
1361
|
public void onCastSessionAvailable() {
|
|
1362
|
+
Uri castUri = getCastUri();
|
|
1363
|
+
if (!isNetworkUri(castUri)) {
|
|
1364
|
+
Toast.makeText(
|
|
1365
|
+
context,
|
|
1366
|
+
"Chromecast requires a network-reachable media URL",
|
|
1367
|
+
Toast.LENGTH_SHORT
|
|
1368
|
+
).show();
|
|
1369
|
+
castContext.getSessionManager().endCurrentSession(false);
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1380
1372
|
isCastSession = true;
|
|
1381
1373
|
final Long videoPosition = player.getCurrentPosition();
|
|
1374
|
+
final boolean shouldPlay = player.getPlayWhenReady();
|
|
1382
1375
|
if (pipEnabled) {
|
|
1383
1376
|
pipBtn.setVisibility(View.GONE);
|
|
1384
1377
|
}
|
|
1385
1378
|
resizeBtn.setVisibility(View.GONE);
|
|
1386
1379
|
player.setPlayWhenReady(false);
|
|
1387
1380
|
cast_image.setVisibility(View.VISIBLE);
|
|
1388
|
-
castPlayer.setMediaItem(mediaItem, videoPosition);
|
|
1389
1381
|
styledPlayerView.setPlayer(castPlayer);
|
|
1382
|
+
castPlayer.setMediaItem(buildCastMediaItem(castUri), videoPosition);
|
|
1383
|
+
castPlayer.setPlayWhenReady(shouldPlay);
|
|
1384
|
+
castPlayer.prepare();
|
|
1390
1385
|
styledPlayerView.setControllerShowTimeoutMs(0);
|
|
1391
1386
|
styledPlayerView.setControllerHideOnTouch(false);
|
|
1392
1387
|
//We perform a click because for some weird reason, the layout is black until the user clicks on it
|
|
@@ -1395,16 +1390,20 @@ public class FullscreenExoPlayerFragment extends Fragment {
|
|
|
1395
1390
|
|
|
1396
1391
|
@Override
|
|
1397
1392
|
public void onCastSessionUnavailable() {
|
|
1393
|
+
if (!isCastSession) return;
|
|
1398
1394
|
isCastSession = false;
|
|
1399
1395
|
final Long videoPosition = castPlayer.getCurrentPosition();
|
|
1396
|
+
final boolean shouldPlay = castPlayer.getPlayWhenReady();
|
|
1400
1397
|
if (pipEnabled) {
|
|
1401
1398
|
pipBtn.setVisibility(View.VISIBLE);
|
|
1402
1399
|
}
|
|
1403
1400
|
resizeBtn.setVisibility(View.VISIBLE);
|
|
1404
1401
|
cast_image.setVisibility(View.GONE);
|
|
1402
|
+
castPlayer.stop();
|
|
1403
|
+
castPlayer.clearMediaItems();
|
|
1405
1404
|
styledPlayerView.setPlayer(player);
|
|
1406
|
-
player.setPlayWhenReady(true);
|
|
1407
1405
|
player.seekTo(videoPosition);
|
|
1406
|
+
player.setPlayWhenReady(shouldPlay);
|
|
1408
1407
|
styledPlayerView.setControllerShowTimeoutMs(3000);
|
|
1409
1408
|
styledPlayerView.setControllerHideOnTouch(true);
|
|
1410
1409
|
}
|
|
@@ -1418,7 +1417,7 @@ public class FullscreenExoPlayerFragment extends Fragment {
|
|
|
1418
1417
|
Map<String, Object> info = new HashMap<String, Object>() {
|
|
1419
1418
|
{
|
|
1420
1419
|
put("fromPlayerId", playerId);
|
|
1421
|
-
put("currentTime", String.valueOf(
|
|
1420
|
+
put("currentTime", String.valueOf(castPlayer.getCurrentPosition() / 1000));
|
|
1422
1421
|
}
|
|
1423
1422
|
};
|
|
1424
1423
|
switch (state) {
|
|
@@ -1449,6 +1448,261 @@ public class FullscreenExoPlayerFragment extends Fragment {
|
|
|
1449
1448
|
|
|
1450
1449
|
private final class EmptyCallback extends MediaRouter.Callback {}
|
|
1451
1450
|
|
|
1451
|
+
private Uri getCastUri() {
|
|
1452
|
+
return uri != null ? uri : Uri.parse(videoPath);
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
private boolean isNetworkUri(Uri mediaUri) {
|
|
1456
|
+
String scheme = mediaUri.getScheme();
|
|
1457
|
+
return "http".equalsIgnoreCase(scheme) || "https".equalsIgnoreCase(scheme);
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
private MediaItem buildCastMediaItem(Uri castUri) {
|
|
1461
|
+
MediaItem.Builder builder = buildMediaItem(castUri).buildUpon().setMediaMetadata(buildCastMediaMetadata());
|
|
1462
|
+
String mimeType = getMediaItemMimeType(castUri);
|
|
1463
|
+
if (mimeType != null) {
|
|
1464
|
+
builder.setMimeType(mimeType);
|
|
1465
|
+
}
|
|
1466
|
+
if (sturi != null && isNetworkUri(sturi)) {
|
|
1467
|
+
builder.setSubtitleConfigurations(Arrays.asList(buildSubtitleConfiguration(sturi, getMimeType(sturi))));
|
|
1468
|
+
}
|
|
1469
|
+
return builder.build();
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
private MediaMetadata buildCastMediaMetadata() {
|
|
1473
|
+
MediaMetadata.Builder metadataBuilder = new MediaMetadata.Builder().setTitle(title).setSubtitle(smallTitle);
|
|
1474
|
+
if (hasArtwork()) {
|
|
1475
|
+
metadataBuilder.setMediaType(MediaMetadata.MEDIA_TYPE_MOVIE).setArtworkUri(Uri.parse(artwork));
|
|
1476
|
+
}
|
|
1477
|
+
return metadataBuilder.build();
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
private boolean hasArtwork() {
|
|
1481
|
+
return artwork != null && !artwork.isEmpty();
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
private MediaItem.SubtitleConfiguration buildSubtitleConfiguration(Uri subtitleUri, String mimeType) {
|
|
1485
|
+
String languageLabel = Locale.forLanguageTag(language).getDisplayLanguage();
|
|
1486
|
+
return new MediaItem.SubtitleConfiguration.Builder(subtitleUri)
|
|
1487
|
+
.setMimeType(mimeType)
|
|
1488
|
+
.setUri(subtitleUri)
|
|
1489
|
+
.setId(subTitle)
|
|
1490
|
+
.setLabel(languageLabel)
|
|
1491
|
+
.setRoleFlags(C.ROLE_FLAG_CAPTION)
|
|
1492
|
+
.setSelectionFlags(C.SELECTION_FLAG_DEFAULT)
|
|
1493
|
+
.setLanguage(language)
|
|
1494
|
+
.build();
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
private String getMediaItemMimeType(Uri mediaUri) {
|
|
1498
|
+
String mediaType = vType != null ? vType : getVideoType(mediaUri);
|
|
1499
|
+
switch (mediaType) {
|
|
1500
|
+
case "dash":
|
|
1501
|
+
case "mpd":
|
|
1502
|
+
return MimeTypes.APPLICATION_MPD;
|
|
1503
|
+
case "m3u8":
|
|
1504
|
+
return MimeTypes.APPLICATION_M3U8;
|
|
1505
|
+
case "ism":
|
|
1506
|
+
return MimeTypes.APPLICATION_SS;
|
|
1507
|
+
case "webm":
|
|
1508
|
+
return MimeTypes.VIDEO_WEBM;
|
|
1509
|
+
case "ogv":
|
|
1510
|
+
return "video/ogg";
|
|
1511
|
+
case "3gp":
|
|
1512
|
+
return "video/3gpp";
|
|
1513
|
+
case "flv":
|
|
1514
|
+
return "video/x-flv";
|
|
1515
|
+
case "mp4":
|
|
1516
|
+
return MimeTypes.VIDEO_MP4;
|
|
1517
|
+
case "ytube":
|
|
1518
|
+
case "":
|
|
1519
|
+
default:
|
|
1520
|
+
return null;
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
private final class SubtitleMediaItemConverter implements MediaItemConverter {
|
|
1525
|
+
|
|
1526
|
+
private static final String KEY_MEDIA_ITEM = "mediaItem";
|
|
1527
|
+
private static final String KEY_PLAYER_CONFIG = "exoPlayerConfig";
|
|
1528
|
+
private static final String KEY_MEDIA_ID = "mediaId";
|
|
1529
|
+
private static final String KEY_URI = "uri";
|
|
1530
|
+
private static final String KEY_TITLE = "title";
|
|
1531
|
+
private static final String KEY_MIME_TYPE = "mimeType";
|
|
1532
|
+
private static final String KEY_DRM_CONFIGURATION = "drmConfiguration";
|
|
1533
|
+
private static final String KEY_UUID = "uuid";
|
|
1534
|
+
private static final String KEY_LICENSE_URI = "licenseUri";
|
|
1535
|
+
private static final String KEY_REQUEST_HEADERS = "requestHeaders";
|
|
1536
|
+
|
|
1537
|
+
private final DefaultMediaItemConverter defaultConverter = new DefaultMediaItemConverter();
|
|
1538
|
+
|
|
1539
|
+
@Override
|
|
1540
|
+
public MediaQueueItem toMediaQueueItem(MediaItem mediaItem) {
|
|
1541
|
+
MediaItem.LocalConfiguration localConfiguration = mediaItem.localConfiguration;
|
|
1542
|
+
if (localConfiguration == null) {
|
|
1543
|
+
throw new IllegalArgumentException("The item must specify its local configuration");
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
MediaInfo.Builder mediaInfoBuilder = new MediaInfo.Builder(getContentId(mediaItem, localConfiguration))
|
|
1547
|
+
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
|
|
1548
|
+
.setContentUrl(localConfiguration.uri.toString())
|
|
1549
|
+
.setMetadata(buildCastMediaInfoMetadata(mediaItem))
|
|
1550
|
+
.setCustomData(buildCastCustomData(mediaItem));
|
|
1551
|
+
|
|
1552
|
+
if (localConfiguration.mimeType != null) {
|
|
1553
|
+
mediaInfoBuilder.setContentType(localConfiguration.mimeType);
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
ArrayList<Long> activeTrackIds = new ArrayList<>();
|
|
1557
|
+
List<MediaTrack> mediaTracks = buildCastSubtitleTracks(localConfiguration.subtitleConfigurations, activeTrackIds);
|
|
1558
|
+
if (!mediaTracks.isEmpty()) {
|
|
1559
|
+
mediaInfoBuilder.setMediaTracks(mediaTracks);
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
MediaQueueItem.Builder mediaQueueItemBuilder = new MediaQueueItem.Builder(mediaInfoBuilder.build());
|
|
1563
|
+
if (!activeTrackIds.isEmpty()) {
|
|
1564
|
+
mediaQueueItemBuilder.setActiveTrackIds(toLongArray(activeTrackIds));
|
|
1565
|
+
}
|
|
1566
|
+
return mediaQueueItemBuilder.build();
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
@Override
|
|
1570
|
+
public MediaItem toMediaItem(MediaQueueItem mediaQueueItem) {
|
|
1571
|
+
return defaultConverter.toMediaItem(mediaQueueItem);
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
private String getContentId(MediaItem mediaItem, MediaItem.LocalConfiguration localConfiguration) {
|
|
1575
|
+
return mediaItem.mediaId.equals(MediaItem.DEFAULT_MEDIA_ID) ? localConfiguration.uri.toString() : mediaItem.mediaId;
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
private com.google.android.gms.cast.MediaMetadata buildCastMediaInfoMetadata(MediaItem mediaItem) {
|
|
1579
|
+
MediaItem.LocalConfiguration localConfiguration = mediaItem.localConfiguration;
|
|
1580
|
+
String mimeType = localConfiguration != null ? localConfiguration.mimeType : null;
|
|
1581
|
+
com.google.android.gms.cast.MediaMetadata metadata = new com.google.android.gms.cast.MediaMetadata(
|
|
1582
|
+
mimeType != null && MimeTypes.isAudio(mimeType)
|
|
1583
|
+
? com.google.android.gms.cast.MediaMetadata.MEDIA_TYPE_MUSIC_TRACK
|
|
1584
|
+
: com.google.android.gms.cast.MediaMetadata.MEDIA_TYPE_MOVIE
|
|
1585
|
+
);
|
|
1586
|
+
|
|
1587
|
+
if (mediaItem.mediaMetadata.title != null) {
|
|
1588
|
+
metadata.putString(com.google.android.gms.cast.MediaMetadata.KEY_TITLE, mediaItem.mediaMetadata.title.toString());
|
|
1589
|
+
}
|
|
1590
|
+
if (mediaItem.mediaMetadata.subtitle != null) {
|
|
1591
|
+
metadata.putString(com.google.android.gms.cast.MediaMetadata.KEY_SUBTITLE, mediaItem.mediaMetadata.subtitle.toString());
|
|
1592
|
+
}
|
|
1593
|
+
if (mediaItem.mediaMetadata.artworkUri != null) {
|
|
1594
|
+
metadata.addImage(new WebImage(mediaItem.mediaMetadata.artworkUri));
|
|
1595
|
+
}
|
|
1596
|
+
return metadata;
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
private List<MediaTrack> buildCastSubtitleTracks(
|
|
1600
|
+
List<MediaItem.SubtitleConfiguration> subtitleConfigurations,
|
|
1601
|
+
ArrayList<Long> activeTrackIds
|
|
1602
|
+
) {
|
|
1603
|
+
ArrayList<MediaTrack> mediaTracks = new ArrayList<>();
|
|
1604
|
+
for (int i = 0; i < subtitleConfigurations.size(); i++) {
|
|
1605
|
+
MediaItem.SubtitleConfiguration subtitleConfiguration = subtitleConfigurations.get(i);
|
|
1606
|
+
if (subtitleConfiguration.uri == null) {
|
|
1607
|
+
continue;
|
|
1608
|
+
}
|
|
1609
|
+
long trackId = i + 1L;
|
|
1610
|
+
MediaTrack.Builder trackBuilder = new MediaTrack.Builder(trackId, MediaTrack.TYPE_TEXT)
|
|
1611
|
+
.setContentId(subtitleConfiguration.uri.toString())
|
|
1612
|
+
.setSubtype(MediaTrack.SUBTYPE_SUBTITLES);
|
|
1613
|
+
if (subtitleConfiguration.mimeType != null) {
|
|
1614
|
+
trackBuilder.setContentType(subtitleConfiguration.mimeType);
|
|
1615
|
+
}
|
|
1616
|
+
if (subtitleConfiguration.label != null) {
|
|
1617
|
+
trackBuilder.setName(subtitleConfiguration.label.toString());
|
|
1618
|
+
}
|
|
1619
|
+
if (subtitleConfiguration.language != null) {
|
|
1620
|
+
trackBuilder.setLanguage(subtitleConfiguration.language);
|
|
1621
|
+
}
|
|
1622
|
+
mediaTracks.add(trackBuilder.build());
|
|
1623
|
+
if ((subtitleConfiguration.selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0) {
|
|
1624
|
+
activeTrackIds.add(trackId);
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
return mediaTracks;
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
private long[] toLongArray(List<Long> values) {
|
|
1631
|
+
long[] result = new long[values.size()];
|
|
1632
|
+
for (int i = 0; i < values.size(); i++) {
|
|
1633
|
+
result[i] = values.get(i);
|
|
1634
|
+
}
|
|
1635
|
+
return result;
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
private JSONObject buildCastCustomData(MediaItem mediaItem) {
|
|
1639
|
+
try {
|
|
1640
|
+
JSONObject customData = new JSONObject();
|
|
1641
|
+
customData.put(KEY_MEDIA_ITEM, buildMediaItemJson(mediaItem));
|
|
1642
|
+
JSONObject playerConfig = buildPlayerConfigJson(mediaItem);
|
|
1643
|
+
if (playerConfig != null) {
|
|
1644
|
+
customData.put(KEY_PLAYER_CONFIG, playerConfig);
|
|
1645
|
+
}
|
|
1646
|
+
return customData;
|
|
1647
|
+
} catch (JSONException e) {
|
|
1648
|
+
throw new RuntimeException(e);
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
private JSONObject buildMediaItemJson(MediaItem mediaItem) throws JSONException {
|
|
1653
|
+
MediaItem.LocalConfiguration localConfiguration = mediaItem.localConfiguration;
|
|
1654
|
+
JSONObject json = new JSONObject();
|
|
1655
|
+
json.put(KEY_MEDIA_ID, mediaItem.mediaId);
|
|
1656
|
+
if (mediaItem.mediaMetadata.title != null) {
|
|
1657
|
+
json.put(KEY_TITLE, mediaItem.mediaMetadata.title.toString());
|
|
1658
|
+
}
|
|
1659
|
+
json.put(KEY_URI, localConfiguration.uri.toString());
|
|
1660
|
+
if (localConfiguration.mimeType != null) {
|
|
1661
|
+
json.put(KEY_MIME_TYPE, localConfiguration.mimeType);
|
|
1662
|
+
}
|
|
1663
|
+
if (localConfiguration.drmConfiguration != null) {
|
|
1664
|
+
json.put(KEY_DRM_CONFIGURATION, buildDrmConfigurationJson(localConfiguration.drmConfiguration));
|
|
1665
|
+
}
|
|
1666
|
+
return json;
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
private JSONObject buildDrmConfigurationJson(MediaItem.DrmConfiguration drmConfiguration) throws JSONException {
|
|
1670
|
+
JSONObject json = new JSONObject();
|
|
1671
|
+
json.put(KEY_UUID, drmConfiguration.scheme.toString());
|
|
1672
|
+
json.put(KEY_LICENSE_URI, drmConfiguration.licenseUri.toString());
|
|
1673
|
+
json.put(KEY_REQUEST_HEADERS, new JSONObject(drmConfiguration.licenseRequestHeaders));
|
|
1674
|
+
return json;
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
private JSONObject buildPlayerConfigJson(MediaItem mediaItem) throws JSONException {
|
|
1678
|
+
MediaItem.LocalConfiguration localConfiguration = mediaItem.localConfiguration;
|
|
1679
|
+
if (localConfiguration == null || localConfiguration.drmConfiguration == null) {
|
|
1680
|
+
return null;
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
MediaItem.DrmConfiguration drmConfiguration = localConfiguration.drmConfiguration;
|
|
1684
|
+
String protectionSystem;
|
|
1685
|
+
if (C.WIDEVINE_UUID.equals(drmConfiguration.scheme)) {
|
|
1686
|
+
protectionSystem = "widevine";
|
|
1687
|
+
} else if (C.PLAYREADY_UUID.equals(drmConfiguration.scheme)) {
|
|
1688
|
+
protectionSystem = "playready";
|
|
1689
|
+
} else {
|
|
1690
|
+
return null;
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
JSONObject json = new JSONObject();
|
|
1694
|
+
json.put("withCredentials", false);
|
|
1695
|
+
json.put("protectionSystem", protectionSystem);
|
|
1696
|
+
if (drmConfiguration.licenseUri != null) {
|
|
1697
|
+
json.put("licenseUrl", drmConfiguration.licenseUri.toString());
|
|
1698
|
+
}
|
|
1699
|
+
if (!drmConfiguration.licenseRequestHeaders.isEmpty()) {
|
|
1700
|
+
json.put("headers", new JSONObject(drmConfiguration.licenseRequestHeaders));
|
|
1701
|
+
}
|
|
1702
|
+
return json;
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1452
1706
|
@Override
|
|
1453
1707
|
public void onConfigurationChanged(Configuration newConfig) {
|
|
1454
1708
|
super.onConfigurationChanged(newConfig);
|
|
@@ -38,7 +38,7 @@ import java.util.Map;
|
|
|
38
38
|
)
|
|
39
39
|
public class VideoPlayerPlugin extends Plugin {
|
|
40
40
|
|
|
41
|
-
private final String pluginVersion = "8.1.
|
|
41
|
+
private final String pluginVersion = "8.1.8";
|
|
42
42
|
|
|
43
43
|
// Permission alias constants
|
|
44
44
|
private static final String PERMISSION_DENIED_ERROR = "Unable to access media videos, user denied permission request";
|
|
@@ -8,7 +8,7 @@ import AVKit
|
|
|
8
8
|
*/
|
|
9
9
|
@objc(VideoPlayerPlugin)
|
|
10
10
|
public class VideoPlayerPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
11
|
-
private let pluginVersion: String = "8.1.
|
|
11
|
+
private let pluginVersion: String = "8.1.8"
|
|
12
12
|
public let identifier = "VideoPlayerPlugin"
|
|
13
13
|
public let jsName = "VideoPlayer"
|
|
14
14
|
public let pluginMethods: [CAPPluginMethod] = [
|