Skip to content

Commit 6faa4eb

Browse files
StaehliJMGaetan89
andauthored
871 provide a mediaitemconverter for pillarbox (#883)
Co-authored-by: Gaëtan Muller <[email protected]>
1 parent c72fcb5 commit 6faa4eb

File tree

22 files changed

+644
-117
lines changed

22 files changed

+644
-117
lines changed

.github/workflows/quality.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ jobs:
149149
:pillarbox-analytics:koverXmlReportDebug
150150
:pillarbox-cast:koverXmlReportDebug
151151
:pillarbox-core-business:koverXmlReportDebug
152+
:pillarbox-core-business-cast:koverXmlReportDebug
152153
:pillarbox-player:koverXmlReportDebug
153154
:pillarbox-ui:koverXmlReportDebug
154155
- name: Report Code Coverage

build.gradle.kts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,13 @@ dependencyAnalysis {
9898
}
9999
}
100100

101+
project(":pillarbox-core-business-cast") {
102+
onUnusedDependencies {
103+
// This dependency is not used directly, but needed to get Cast dependencies
104+
exclude(":pillarbox-cast")
105+
}
106+
}
107+
101108
project(":pillarbox-player") {
102109
onUnusedDependencies {
103110
// These dependencies are not used directly, but automatically used by libs.androidx.media3.exoplayer

pillarbox-cast/src/main/java/ch/srgssr/pillarbox/cast/CastExtensions.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package ch.srgssr.pillarbox.cast
66

77
import android.content.Context
88
import com.google.android.gms.cast.framework.CastContext
9+
import com.google.android.gms.cast.framework.CastState
910
import com.google.common.util.concurrent.MoreExecutors
1011

1112
/**
@@ -22,3 +23,10 @@ import com.google.common.util.concurrent.MoreExecutors
2223
fun Context.getCastContext(): CastContext {
2324
return CastContext.getSharedInstance() ?: CastContext.getSharedInstance(this, MoreExecutors.directExecutor()).result
2425
}
26+
27+
/**
28+
* @return if the Cast is connected.
29+
*/
30+
fun CastContext.isConnected(): Boolean {
31+
return castState == CastState.CONNECTED || castState == CastState.CONNECTING
32+
}
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
/*
2+
* Copyright (c) SRG SSR. All rights reserved.
3+
* License information is available from the LICENSE file.
4+
*/
5+
package ch.srgssr.pillarbox.cast
6+
7+
import androidx.media3.common.AudioAttributes
8+
import androidx.media3.common.DeviceInfo
9+
import androidx.media3.common.FlagSet
10+
import androidx.media3.common.MediaItem
11+
import androidx.media3.common.MediaMetadata
12+
import androidx.media3.common.Metadata
13+
import androidx.media3.common.PlaybackException
14+
import androidx.media3.common.PlaybackParameters
15+
import androidx.media3.common.Player
16+
import androidx.media3.common.Timeline
17+
import androidx.media3.common.TrackSelectionParameters
18+
import androidx.media3.common.Tracks
19+
import androidx.media3.common.VideoSize
20+
import androidx.media3.common.text.CueGroup
21+
22+
/**
23+
* Cast forwarding listener
24+
*
25+
* Because kotlin delegation with interface with default method are not handled correctly we have to implement all listener method.
26+
*
27+
* https://github.com/androidx/media/issues/2120
28+
*
29+
* @param player The player to forward to [onEvents].
30+
* @param listener The [Player.Listener] to forward to.
31+
*/
32+
internal class CastForwardingListener(
33+
private val player: Player,
34+
private val listener: Player.Listener
35+
) : Player.Listener by listener {
36+
37+
override fun onTracksChanged(tracks: Tracks) {
38+
// Do not forward this event.
39+
}
40+
41+
override fun onAvailableCommandsChanged(availableCommands: Player.Commands) {
42+
// Do not forward this event.
43+
}
44+
45+
override fun onEvents(player: Player, events: Player.Events) {
46+
// Filter Events triggered by CastPlayer that PillarboxCastPlayer handles
47+
val flagSet = FlagSet.Builder()
48+
.apply {
49+
for (index in 0 until events.size()) {
50+
val event = events.get(index)
51+
addIf(event, event != Player.EVENT_TRACKS_CHANGED && event != Player.EVENT_AVAILABLE_COMMANDS_CHANGED)
52+
}
53+
}
54+
.build()
55+
listener.onEvents(this.player, Player.Events(flagSet))
56+
}
57+
58+
override fun onTimelineChanged(timeline: Timeline, reason: Int) {
59+
listener.onTimelineChanged(timeline, reason)
60+
}
61+
62+
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
63+
listener.onMediaItemTransition(mediaItem, reason)
64+
}
65+
66+
override fun onMediaMetadataChanged(mediaMetadata: MediaMetadata) {
67+
listener.onMediaMetadataChanged(mediaMetadata)
68+
}
69+
70+
override fun onPlaylistMetadataChanged(mediaMetadata: MediaMetadata) {
71+
listener.onPlaylistMetadataChanged(mediaMetadata)
72+
}
73+
74+
override fun onIsLoadingChanged(isLoading: Boolean) {
75+
listener.onIsLoadingChanged(isLoading)
76+
}
77+
78+
override fun onTrackSelectionParametersChanged(parameters: TrackSelectionParameters) {
79+
listener.onTrackSelectionParametersChanged(parameters)
80+
}
81+
82+
override fun onPlaybackStateChanged(playbackState: Int) {
83+
listener.onPlaybackStateChanged(playbackState)
84+
}
85+
86+
override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) {
87+
listener.onPlayWhenReadyChanged(playWhenReady, reason)
88+
}
89+
90+
override fun onPlaybackSuppressionReasonChanged(playbackSuppressionReason: Int) {
91+
listener.onPlaybackSuppressionReasonChanged(playbackSuppressionReason)
92+
}
93+
94+
override fun onIsPlayingChanged(isPlaying: Boolean) {
95+
listener.onIsPlayingChanged(isPlaying)
96+
}
97+
98+
override fun onRepeatModeChanged(repeatMode: Int) {
99+
listener.onRepeatModeChanged(repeatMode)
100+
}
101+
102+
override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {
103+
listener.onShuffleModeEnabledChanged(shuffleModeEnabled)
104+
}
105+
106+
override fun onPlayerError(error: PlaybackException) {
107+
listener.onPlayerError(error)
108+
}
109+
110+
override fun onPlayerErrorChanged(error: PlaybackException?) {
111+
listener.onPlayerErrorChanged(error)
112+
}
113+
114+
override fun onPositionDiscontinuity(oldPosition: Player.PositionInfo, newPosition: Player.PositionInfo, reason: Int) {
115+
listener.onPositionDiscontinuity(oldPosition, newPosition, reason)
116+
}
117+
118+
override fun onPlaybackParametersChanged(playbackParameters: PlaybackParameters) {
119+
listener.onPlaybackParametersChanged(playbackParameters)
120+
}
121+
122+
override fun onSeekBackIncrementChanged(seekBackIncrementMs: Long) {
123+
listener.onSeekBackIncrementChanged(seekBackIncrementMs)
124+
}
125+
126+
override fun onSeekForwardIncrementChanged(seekForwardIncrementMs: Long) {
127+
listener.onSeekForwardIncrementChanged(seekForwardIncrementMs)
128+
}
129+
130+
override fun onMaxSeekToPreviousPositionChanged(maxSeekToPreviousPositionMs: Long) {
131+
listener.onMaxSeekToPreviousPositionChanged(maxSeekToPreviousPositionMs)
132+
}
133+
134+
override fun onAudioAttributesChanged(audioAttributes: AudioAttributes) {
135+
listener.onAudioAttributesChanged(audioAttributes)
136+
}
137+
138+
override fun onVolumeChanged(volume: Float) {
139+
listener.onVolumeChanged(volume)
140+
}
141+
142+
override fun onSkipSilenceEnabledChanged(skipSilenceEnabled: Boolean) {
143+
listener.onSkipSilenceEnabledChanged(skipSilenceEnabled)
144+
}
145+
146+
override fun onDeviceInfoChanged(deviceInfo: DeviceInfo) {
147+
listener.onDeviceInfoChanged(deviceInfo)
148+
}
149+
150+
override fun onDeviceVolumeChanged(volume: Int, muted: Boolean) {
151+
listener.onDeviceVolumeChanged(volume, muted)
152+
}
153+
154+
override fun onVideoSizeChanged(videoSize: VideoSize) {
155+
listener.onVideoSizeChanged(videoSize)
156+
}
157+
158+
override fun onSurfaceSizeChanged(width: Int, height: Int) {
159+
listener.onSurfaceSizeChanged(width, height)
160+
}
161+
162+
override fun onRenderedFirstFrame() {
163+
listener.onRenderedFirstFrame()
164+
}
165+
166+
override fun onCues(cueGroup: CueGroup) {
167+
listener.onCues(cueGroup)
168+
}
169+
170+
override fun onMetadata(metadata: Metadata) {
171+
listener.onMetadata(metadata)
172+
}
173+
174+
override fun equals(other: Any?): Boolean {
175+
if (this === other) return true
176+
if (javaClass != other?.javaClass) return false
177+
178+
other as CastForwardingListener
179+
180+
if (player != other.player) return false
181+
if (listener != other.listener) return false
182+
183+
return true
184+
}
185+
186+
override fun hashCode(): Int {
187+
var result = player.hashCode()
188+
result = 31 * result + listener.hashCode()
189+
return result
190+
}
191+
}

pillarbox-cast/src/main/java/ch/srgssr/pillarbox/cast/PillarboxCastPlayer.kt

Lines changed: 2 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import androidx.media3.cast.DefaultMediaItemConverter
1212
import androidx.media3.cast.MediaItemConverter
1313
import androidx.media3.cast.SessionAvailabilityListener
1414
import androidx.media3.common.C
15-
import androidx.media3.common.FlagSet
1615
import androidx.media3.common.Format
1716
import androidx.media3.common.MediaItem
1817
import androidx.media3.common.MimeTypes
@@ -170,13 +169,13 @@ class PillarboxCastPlayer(
170169
}
171170

172171
override fun addListener(listener: Player.Listener) {
173-
castPlayer.addListener(ForwardingListener(this, listener))
172+
castPlayer.addListener(CastForwardingListener(this, listener))
174173
listeners.add(listener)
175174
}
176175

177176
@SuppressLint("ImplicitSamInstance")
178177
override fun removeListener(listener: Player.Listener) {
179-
castPlayer.removeListener(ForwardingListener(this, listener))
178+
castPlayer.removeListener(CastForwardingListener(this, listener))
180179
listeners.remove(listener)
181180
}
182181

@@ -265,54 +264,6 @@ class PillarboxCastPlayer(
265264
}
266265
}
267266

268-
private class ForwardingListener(
269-
private val player: Player,
270-
private val listener: Player.Listener
271-
) : Player.Listener by listener {
272-
273-
override fun onTracksChanged(tracks: Tracks) {
274-
// Do not forward this event.
275-
}
276-
277-
override fun onAvailableCommandsChanged(availableCommands: Player.Commands) {
278-
// Do not forward this event.
279-
}
280-
281-
override fun onEvents(player: Player, events: Player.Events) {
282-
// Filter Events triggered by CastPlayer
283-
if (events.containsAny(Player.EVENT_AVAILABLE_COMMANDS_CHANGED, Player.EVENT_TRACKS_CHANGED)) {
284-
return
285-
}
286-
val flagSet = FlagSet.Builder()
287-
.apply {
288-
for (index in 0 until events.size()) {
289-
val event = events.get(index)
290-
addIf(event, event != Player.EVENT_TRACKS_CHANGED && event != Player.EVENT_AVAILABLE_COMMANDS_CHANGED)
291-
}
292-
}
293-
.build()
294-
listener.onEvents(this.player, Player.Events(flagSet))
295-
}
296-
297-
override fun equals(other: Any?): Boolean {
298-
if (this === other) return true
299-
if (javaClass != other?.javaClass) return false
300-
301-
other as ForwardingListener
302-
303-
if (player != other.player) return false
304-
if (listener != other.listener) return false
305-
306-
return true
307-
}
308-
309-
override fun hashCode(): Int {
310-
var result = player.hashCode()
311-
result = 31 * result + listener.hashCode()
312-
return result
313-
}
314-
}
315-
316267
private inner class InternalCastPlayerListener : Player.Listener {
317268
override fun onAvailableCommandsChanged(availableCommands: Player.Commands) {
318269
notifyOnAvailableCommandsChange()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright (c) SRG SSR. All rights reserved.
3+
* License information is available from the LICENSE file.
4+
*/
5+
6+
plugins {
7+
alias(libs.plugins.pillarbox.android.library)
8+
alias(libs.plugins.pillarbox.android.library.publishing)
9+
alias(libs.plugins.pillarbox.android.library.tested.module)
10+
}
11+
12+
dependencies {
13+
api(project(":pillarbox-core-business"))
14+
api(project(":pillarbox-cast"))
15+
testImplementation(libs.androidx.test.core)
16+
testImplementation(libs.androidx.test.ext.junit)
17+
testImplementation(libs.junit)
18+
testImplementation(libs.mockk)
19+
testRuntimeOnly(libs.robolectric)
20+
testImplementation(libs.robolectric.shadows.framework)
21+
}

pillarbox-core-business-cast/consumer-rules.pro

Whitespace-only changes.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Add project specific ProGuard rules here.
2+
# You can control the set of applied configuration files using the
3+
# proguardFiles setting in build.gradle.
4+
#
5+
# For more details, see
6+
# http://developer.android.com/guide/developing/tools/proguard.html
7+
8+
# If your project uses WebView with JS, uncomment the following
9+
# and specify the fully qualified class name to the JavaScript interface
10+
# class:
11+
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12+
# public *;
13+
#}
14+
15+
# Uncomment this to preserve the line number information for
16+
# debugging stack traces.
17+
#-keepattributes SourceFile,LineNumberTable
18+
19+
# If you keep the line number information, uncomment this to
20+
# hide the original source file name.
21+
#-renamesourcefileattribute SourceFile
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!--
3+
~ Copyright (c) SRG SSR. All rights reserved.
4+
~ License information is available from the LICENSE file.
5+
-->
6+
<manifest />

0 commit comments

Comments
 (0)