Skip to content

Commit 5ab436b

Browse files
Add media Play / Pause actions for the livestream notification (#1461)
* Manage mediaSession objects separately * Update test * Spotless & API * Update to use client scope for updates * Add pause/play action in demo app * Enable custom callback and interceptor * Remove delay * Allow overriding media style * Remove duration
1 parent 27e1b0e commit 5ab436b

5 files changed

Lines changed: 167 additions & 0 deletions

File tree

demo-app/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,9 @@ dependencies {
223223
compileOnly(project(":stream-video-android-previewdata"))
224224

225225

226+
implementation(libs.androidx.media.media)
227+
228+
226229
implementation(libs.androidx.media.media)
227230

228231
// Noise Cancellation

demo-app/src/main/AndroidManifest.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,12 @@
9090
</intent-filter>
9191
</activity>
9292

93+
<receiver android:name="androidx.media.session.MediaButtonReceiver"
94+
android:exported="true">
95+
<intent-filter>
96+
<action android:name="android.intent.action.MEDIA_BUTTON" />
97+
</intent-filter>
98+
</receiver>
9399
<!-- Prevent firebase from using advertisement ID -->
94100
<meta-data
95101
android:name="google_analytics_adid_collection_enabled"
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
* Copyright (c) 2014-2024 Stream.io Inc. All rights reserved.
3+
*
4+
* Licensed under the Stream License;
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://github.com/GetStream/stream-video-android/blob/main/LICENSE
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.getstream.video.android.notification
18+
19+
import android.content.Context
20+
import android.graphics.Bitmap
21+
import android.graphics.BitmapFactory
22+
import android.support.v4.media.MediaMetadataCompat
23+
import android.support.v4.media.session.PlaybackStateCompat
24+
import android.util.Log
25+
import androidx.core.app.NotificationCompat
26+
import androidx.media.session.MediaButtonReceiver
27+
import io.getstream.video.android.R
28+
import io.getstream.video.android.core.Call
29+
import io.getstream.video.android.core.notifications.handlers.StreamNotificationUpdateInterceptors
30+
import java.net.URL
31+
32+
class LiveStreamMediaNotificationInterceptor(private val context: Context) : StreamNotificationUpdateInterceptors() {
33+
private var bitmap: Bitmap? = null
34+
35+
override suspend fun onUpdateMediaNotificationPlaybackState(
36+
builder: PlaybackStateCompat.Builder,
37+
call: Call,
38+
callDisplayName: String?,
39+
): PlaybackStateCompat.Builder {
40+
builder.setActions(
41+
PlaybackStateCompat.ACTION_PLAY or PlaybackStateCompat.ACTION_PAUSE,
42+
)
43+
return builder
44+
}
45+
46+
override suspend fun onUpdateOngoingCallMediaNotification(
47+
builder: NotificationCompat.Builder,
48+
callDisplayName: String?,
49+
call: Call,
50+
): NotificationCompat.Builder {
51+
val bitmap = getStreamLogoBitmap()
52+
if (bitmap != null) {
53+
builder.setLargeIcon(bitmap)
54+
}
55+
56+
val playAction = NotificationCompat.Action(
57+
android.R.drawable.ic_media_play,
58+
"Play",
59+
MediaButtonReceiver.buildMediaButtonPendingIntent(
60+
context,
61+
PlaybackStateCompat.ACTION_PLAY,
62+
),
63+
)
64+
65+
val pauseAction = NotificationCompat.Action(
66+
android.R.drawable.ic_media_pause,
67+
"Pause",
68+
MediaButtonReceiver.buildMediaButtonPendingIntent(
69+
context,
70+
PlaybackStateCompat.ACTION_PAUSE,
71+
),
72+
)
73+
builder.addAction(playAction)
74+
builder.addAction(pauseAction)
75+
76+
return builder
77+
}
78+
79+
override suspend fun onUpdateMediaNotificationMetadata(
80+
builder: MediaMetadataCompat.Builder,
81+
call: Call,
82+
callDisplayName: String?,
83+
): MediaMetadataCompat.Builder {
84+
val bitmap = getStreamLogoBitmap()
85+
if (bitmap != null) {
86+
Log.d("StreamVideoInitHelper", "Loaded image")
87+
builder.putBitmap(
88+
MediaMetadataCompat.METADATA_KEY_ALBUM_ART,
89+
bitmap,
90+
)
91+
}
92+
return builder
93+
}
94+
95+
private fun getStreamLogoBitmap(): Bitmap? {
96+
if (bitmap == null) {
97+
bitmap = try {
98+
URL("https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ8dzj-6rSfEfYMOXPSCV3s84Luuqr2c9KzMg&s").openStream()
99+
.use { BitmapFactory.decodeStream(it) }
100+
} catch (e: Exception) {
101+
// Fallback
102+
BitmapFactory.decodeResource(context.resources, R.drawable.stream_calls_logo)
103+
}
104+
}
105+
return bitmap
106+
}
107+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright (c) 2014-2024 Stream.io Inc. All rights reserved.
3+
*
4+
* Licensed under the Stream License;
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://github.com/GetStream/stream-video-android/blob/main/LICENSE
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.getstream.video.android.notification
18+
19+
import android.support.v4.media.session.MediaSessionCompat
20+
import android.util.Log
21+
import io.getstream.video.android.core.StreamVideo
22+
23+
/**
24+
* Callback for media session.
25+
*/
26+
class PausePlayMediaSessionCallback : MediaSessionCompat.Callback() {
27+
28+
override fun onPause() {
29+
Log.d("StreamVideoInitHelper", "Pause")
30+
StreamVideo.instanceOrNull()?.state?.activeCall?.value?.debug?.pause()
31+
super.onPause()
32+
}
33+
34+
override fun onPlay() {
35+
Log.d("StreamVideoInitHelper", "Play")
36+
StreamVideo.instanceOrNull()?.state?.activeCall?.value?.debug?.resume()
37+
super.onPlay()
38+
}
39+
}

demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,13 @@ import io.getstream.chat.android.state.plugin.config.StatePluginConfig
2727
import io.getstream.chat.android.state.plugin.factory.StreamStatePluginFactory
2828
import io.getstream.log.Priority
2929
import io.getstream.video.android.BuildConfig
30+
import io.getstream.video.android.app
3031
import io.getstream.video.android.core.StreamVideo
3132
import io.getstream.video.android.core.StreamVideoBuilder
33+
import io.getstream.video.android.core.internal.ExperimentalStreamVideoApi
3234
import io.getstream.video.android.core.logging.LoggingLevel
3335
import io.getstream.video.android.core.notifications.NotificationConfig
36+
import io.getstream.video.android.core.notifications.handlers.CompatibilityStreamNotificationHandler
3437
import io.getstream.video.android.core.notifications.internal.service.CallServiceConfigRegistry
3538
import io.getstream.video.android.core.notifications.internal.service.DefaultCallConfigurations
3639
import io.getstream.video.android.core.socket.common.token.TokenProvider
@@ -40,6 +43,8 @@ import io.getstream.video.android.datastore.delegate.StreamUserDataStore
4043
import io.getstream.video.android.model.ApiKey
4144
import io.getstream.video.android.model.User
4245
import io.getstream.video.android.noise.cancellation.NoiseCancellation
46+
import io.getstream.video.android.notification.LiveStreamMediaNotificationInterceptor
47+
import io.getstream.video.android.notification.PausePlayMediaSessionCallback
4348
import io.getstream.video.android.util.config.AppConfig
4449
import kotlinx.coroutines.flow.MutableStateFlow
4550
import kotlinx.coroutines.flow.StateFlow
@@ -185,6 +190,7 @@ object StreamVideoInitHelper {
185190
}
186191

187192
/** Sets up and returns the [StreamVideo] required to connect to the API. */
193+
@OptIn(ExperimentalStreamVideoApi::class)
188194
private fun initializeStreamVideo(
189195
context: Context,
190196
apiKey: ApiKey,
@@ -212,6 +218,12 @@ object StreamVideoInitHelper {
212218
),
213219
),
214220
hideRingingNotificationInForeground = true,
221+
notificationHandler = CompatibilityStreamNotificationHandler(
222+
application = context.app,
223+
mediaSessionCallback = PausePlayMediaSessionCallback(),
224+
updateNotificationBuilderInterceptor = LiveStreamMediaNotificationInterceptor(context),
225+
hideRingingNotificationInForeground = true,
226+
),
215227
),
216228
tokenProvider = object : TokenProvider {
217229
override suspend fun loadToken(): String {

0 commit comments

Comments
 (0)