Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
SDK
/
exoplayer
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Settings
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
9a70bbca
authored
May 27, 2022
by
bachinger
Committed by
Marc Baechinger
May 30, 2022
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Demonstrate how to use custom actions in the session demo app.
PiperOrigin-RevId: 451410714
parent
6c4f6ecf
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
154 additions
and
65 deletions
demos/session/src/main/java/androidx/media3/demo/session/PlaybackService.kt
demos/session/src/main/java/androidx/media3/demo/session/PlaybackService.kt
View file @
9a70bbca
...
@@ -25,28 +25,113 @@ import android.os.Bundle
...
@@ -25,28 +25,113 @@ import android.os.Bundle
import
androidx.media3.common.AudioAttributes
import
androidx.media3.common.AudioAttributes
import
androidx.media3.common.MediaItem
import
androidx.media3.common.MediaItem
import
androidx.media3.exoplayer.ExoPlayer
import
androidx.media3.exoplayer.ExoPlayer
import
androidx.media3.session.CommandButton
import
androidx.media3.session.LibraryResult
import
androidx.media3.session.LibraryResult
import
androidx.media3.session.MediaLibraryService
import
androidx.media3.session.MediaLibraryService
import
androidx.media3.session.MediaSession
import
androidx.media3.session.MediaSession
import
androidx.media3.session.MediaSession.ControllerInfo
import
androidx.media3.session.SessionCommand
import
androidx.media3.session.SessionResult
import
androidx.media3.session.SessionResult
import
com.google.common.collect.ImmutableList
import
com.google.common.collect.ImmutableList
import
com.google.common.util.concurrent.Futures
import
com.google.common.util.concurrent.Futures
import
com.google.common.util.concurrent.ListenableFuture
import
com.google.common.util.concurrent.ListenableFuture
class
PlaybackService
:
MediaLibraryService
()
{
class
PlaybackService
:
MediaLibraryService
()
{
private
val
librarySessionCallback
=
CustomMediaLibrarySessionCallback
()
private
lateinit
var
player
:
ExoPlayer
private
lateinit
var
player
:
ExoPlayer
private
lateinit
var
mediaLibrarySession
:
MediaLibrarySession
private
lateinit
var
mediaLibrarySession
:
MediaLibrarySession
private
val
librarySessionCallback
=
CustomMediaLibrarySessionCallback
()
private
lateinit
var
customCommands
:
List
<
CommandButton
>
private
var
customLayout
=
ImmutableList
.
of
<
CommandButton
>()
companion
object
{
companion
object
{
private
const
val
SEARCH_QUERY_PREFIX_COMPAT
=
"androidx://media3-session/playFromSearch"
private
const
val
SEARCH_QUERY_PREFIX_COMPAT
=
"androidx://media3-session/playFromSearch"
private
const
val
SEARCH_QUERY_PREFIX
=
"androidx://media3-session/setMediaUri"
private
const
val
SEARCH_QUERY_PREFIX
=
"androidx://media3-session/setMediaUri"
private
const
val
CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON
=
"android.media3.session.demo.SHUFFLE_ON"
private
const
val
CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF
=
"android.media3.session.demo.SHUFFLE_OFF"
}
override
fun
onCreate
()
{
super
.
onCreate
()
customCommands
=
listOf
(
getShuffleCommandButton
(
SessionCommand
(
CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON
,
Bundle
.
EMPTY
)
),
getShuffleCommandButton
(
SessionCommand
(
CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF
,
Bundle
.
EMPTY
)
)
)
customLayout
=
ImmutableList
.
of
(
customCommands
[
0
])
initializeSessionAndPlayer
()
}
override
fun
onGetSession
(
controllerInfo
:
ControllerInfo
):
MediaLibrarySession
{
return
mediaLibrarySession
}
override
fun
onDestroy
()
{
player
.
release
()
mediaLibrarySession
.
release
()
super
.
onDestroy
()
}
}
private
inner
class
CustomMediaLibrarySessionCallback
:
MediaLibrarySession
.
Callback
{
private
inner
class
CustomMediaLibrarySessionCallback
:
MediaLibrarySession
.
Callback
{
override
fun
onConnect
(
session
:
MediaSession
,
controller
:
ControllerInfo
):
MediaSession
.
ConnectionResult
{
val
connectionResult
=
super
.
onConnect
(
session
,
controller
)
val
availableSessionCommands
=
connectionResult
.
availableSessionCommands
.
buildUpon
()
customCommands
.
forEach
{
commandButton
->
// Add custom command to available session commands.
commandButton
.
sessionCommand
?.
let
{
availableSessionCommands
.
add
(
it
)
}
}
return
MediaSession
.
ConnectionResult
.
accept
(
availableSessionCommands
.
build
(),
connectionResult
.
availablePlayerCommands
)
}
override
fun
onPostConnect
(
session
:
MediaSession
,
controller
:
ControllerInfo
)
{
if
(!
customLayout
.
isEmpty
()
&&
controller
.
controllerVersion
!=
0
)
{
// Let Media3 controller (for instance the MediaNotificationProvider) know about the custom
// layout right after it connected.
ignoreFuture
(
mediaLibrarySession
.
setCustomLayout
(
controller
,
customLayout
))
}
}
override
fun
onCustomCommand
(
session
:
MediaSession
,
controller
:
ControllerInfo
,
customCommand
:
SessionCommand
,
args
:
Bundle
):
ListenableFuture
<
SessionResult
>
{
if
(
CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON
==
customCommand
.
customAction
)
{
// Enable shuffling.
player
.
shuffleModeEnabled
=
true
// Change the custom layout to contain the `Disable shuffling` command.
customLayout
=
ImmutableList
.
of
(
customCommands
[
1
])
// Send the updated custom layout to controllers.
session
.
setCustomLayout
(
customLayout
)
}
else
if
(
CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF
==
customCommand
.
customAction
)
{
// Disable shuffling.
player
.
shuffleModeEnabled
=
false
// Change the custom layout to contain the `Enable shuffling` command.
customLayout
=
ImmutableList
.
of
(
customCommands
[
0
])
// Send the updated custom layout to controllers.
session
.
setCustomLayout
(
customLayout
)
}
return
Futures
.
immediateFuture
(
SessionResult
(
SessionResult
.
RESULT_SUCCESS
))
}
override
fun
onGetLibraryRoot
(
override
fun
onGetLibraryRoot
(
session
:
MediaLibrarySession
,
session
:
MediaLibrarySession
,
browser
:
MediaSession
.
ControllerInfo
,
browser
:
ControllerInfo
,
params
:
LibraryParams
?
params
:
LibraryParams
?
):
ListenableFuture
<
LibraryResult
<
MediaItem
>>
{
):
ListenableFuture
<
LibraryResult
<
MediaItem
>>
{
return
Futures
.
immediateFuture
(
LibraryResult
.
ofItem
(
MediaItemTree
.
getRootItem
(),
params
))
return
Futures
.
immediateFuture
(
LibraryResult
.
ofItem
(
MediaItemTree
.
getRootItem
(),
params
))
...
@@ -54,7 +139,7 @@ class PlaybackService : MediaLibraryService() {
...
@@ -54,7 +139,7 @@ class PlaybackService : MediaLibraryService() {
override
fun
onGetItem
(
override
fun
onGetItem
(
session
:
MediaLibrarySession
,
session
:
MediaLibrarySession
,
browser
:
MediaSession
.
ControllerInfo
,
browser
:
ControllerInfo
,
mediaId
:
String
mediaId
:
String
):
ListenableFuture
<
LibraryResult
<
MediaItem
>>
{
):
ListenableFuture
<
LibraryResult
<
MediaItem
>>
{
val
item
=
val
item
=
...
@@ -65,9 +150,24 @@ class PlaybackService : MediaLibraryService() {
...
@@ -65,9 +150,24 @@ class PlaybackService : MediaLibraryService() {
return
Futures
.
immediateFuture
(
LibraryResult
.
ofItem
(
item
,
/* params= */
null
))
return
Futures
.
immediateFuture
(
LibraryResult
.
ofItem
(
item
,
/* params= */
null
))
}
}
override
fun
onSubscribe
(
session
:
MediaLibrarySession
,
browser
:
ControllerInfo
,
parentId
:
String
,
params
:
LibraryParams
?
):
ListenableFuture
<
LibraryResult
<
Void
>>
{
val
children
=
MediaItemTree
.
getChildren
(
parentId
)
?:
return
Futures
.
immediateFuture
(
LibraryResult
.
ofError
(
LibraryResult
.
RESULT_ERROR_BAD_VALUE
)
)
session
.
notifyChildrenChanged
(
browser
,
parentId
,
children
.
size
,
params
)
return
Futures
.
immediateFuture
(
LibraryResult
.
ofVoid
())
}
override
fun
onGetChildren
(
override
fun
onGetChildren
(
session
:
MediaLibrarySession
,
session
:
MediaLibrarySession
,
browser
:
MediaSession
.
ControllerInfo
,
browser
:
ControllerInfo
,
parentId
:
String
,
parentId
:
String
,
page
:
Int
,
page
:
Int
,
pageSize
:
Int
,
pageSize
:
Int
,
...
@@ -82,24 +182,9 @@ class PlaybackService : MediaLibraryService() {
...
@@ -82,24 +182,9 @@ class PlaybackService : MediaLibraryService() {
return
Futures
.
immediateFuture
(
LibraryResult
.
ofItemList
(
children
,
params
))
return
Futures
.
immediateFuture
(
LibraryResult
.
ofItemList
(
children
,
params
))
}
}
private
fun
setMediaItemFromSearchQuery
(
query
:
String
)
{
// Only accept query with pattern "play [Title]" or "[Title]"
// Where [Title]: must be exactly matched
// If no media with exact name found, play a random media instead
val
mediaTitle
=
if
(
query
.
startsWith
(
"play "
,
ignoreCase
=
true
))
{
query
.
drop
(
5
)
}
else
{
query
}
val
item
=
MediaItemTree
.
getItemFromTitle
(
mediaTitle
)
?:
MediaItemTree
.
getRandomItem
()
player
.
setMediaItem
(
item
)
}
override
fun
onSetMediaUri
(
override
fun
onSetMediaUri
(
session
:
MediaSession
,
session
:
MediaSession
,
controller
:
MediaSession
.
ControllerInfo
,
controller
:
ControllerInfo
,
uri
:
Uri
,
uri
:
Uri
,
extras
:
Bundle
extras
:
Bundle
):
Int
{
):
Int
{
...
@@ -107,7 +192,7 @@ class PlaybackService : MediaLibraryService() {
...
@@ -107,7 +192,7 @@ class PlaybackService : MediaLibraryService() {
if
(
uri
.
toString
().
startsWith
(
SEARCH_QUERY_PREFIX
)
||
if
(
uri
.
toString
().
startsWith
(
SEARCH_QUERY_PREFIX
)
||
uri
.
toString
().
startsWith
(
SEARCH_QUERY_PREFIX_COMPAT
)
uri
.
toString
().
startsWith
(
SEARCH_QUERY_PREFIX_COMPAT
)
)
{
)
{
va
r
searchQuery
=
va
l
searchQuery
=
uri
.
getQueryParameter
(
"query"
)
?:
return
SessionResult
.
RESULT_ERROR_NOT_SUPPORTED
uri
.
getQueryParameter
(
"query"
)
?:
return
SessionResult
.
RESULT_ERROR_NOT_SUPPORTED
setMediaItemFromSearchQuery
(
searchQuery
)
setMediaItemFromSearchQuery
(
searchQuery
)
...
@@ -117,47 +202,22 @@ class PlaybackService : MediaLibraryService() {
...
@@ -117,47 +202,22 @@ class PlaybackService : MediaLibraryService() {
}
}
}
}
override
fun
onSubscribe
(
private
fun
setMediaItemFromSearchQuery
(
query
:
String
)
{
session
:
MediaLibrarySession
,
// Only accept query with pattern "play [Title]" or "[Title]"
browser
:
MediaSession
.
ControllerInfo
,
// Where [Title]: must be exactly matched
parentId
:
String
,
// If no media with exact name found, play a random media instead
params
:
LibraryParams
?
val
mediaTitle
=
):
ListenableFuture
<
LibraryResult
<
Void
>>
{
if
(
query
.
startsWith
(
"play "
,
ignoreCase
=
true
))
{
val
children
=
query
.
drop
(
5
)
MediaItemTree
.
getChildren
(
parentId
)
}
else
{
?:
return
Futures
.
immediateFuture
(
query
LibraryResult
.
ofError
(
LibraryResult
.
RESULT_ERROR_BAD_VALUE
)
}
)
session
.
notifyChildrenChanged
(
browser
,
parentId
,
children
.
size
,
params
)
return
Futures
.
immediateFuture
(
LibraryResult
.
ofVoid
())
}
}
private
class
CustomMediaItemFiller
:
MediaSession
.
MediaItemFiller
{
val
item
=
MediaItemTree
.
getItemFromTitle
(
mediaTitle
)
?:
MediaItemTree
.
getRandomItem
()
override
fun
fillInLocalConfiguration
(
player
.
setMediaItem
(
item
)
session
:
MediaSession
,
controller
:
MediaSession
.
ControllerInfo
,
mediaItem
:
MediaItem
):
MediaItem
{
return
MediaItemTree
.
getItem
(
mediaItem
.
mediaId
)
?:
mediaItem
}
}
}
}
override
fun
onCreate
()
{
super
.
onCreate
()
initializeSessionAndPlayer
()
}
override
fun
onDestroy
()
{
player
.
release
()
mediaLibrarySession
.
release
()
super
.
onDestroy
()
}
override
fun
onGetSession
(
controllerInfo
:
MediaSession
.
ControllerInfo
):
MediaLibrarySession
{
return
mediaLibrarySession
}
private
fun
initializeSessionAndPlayer
()
{
private
fun
initializeSessionAndPlayer
()
{
player
=
player
=
ExoPlayer
.
Builder
(
this
)
ExoPlayer
.
Builder
(
this
)
...
@@ -165,13 +225,10 @@ class PlaybackService : MediaLibraryService() {
...
@@ -165,13 +225,10 @@ class PlaybackService : MediaLibraryService() {
.
build
()
.
build
()
MediaItemTree
.
initialize
(
assets
)
MediaItemTree
.
initialize
(
assets
)
val
parentScreenIntent
=
Intent
(
this
,
MainActivity
::
class
.
java
)
val
sessionActivityPendingIntent
=
val
intent
=
Intent
(
this
,
PlayerActivity
::
class
.
java
)
val
pendingIntent
=
TaskStackBuilder
.
create
(
this
).
run
{
TaskStackBuilder
.
create
(
this
).
run
{
addNextIntent
(
parentScreenIntent
)
addNextIntent
(
Intent
(
this
@PlaybackService
,
MainActivity
::
class
.
java
)
)
addNextIntent
(
intent
)
addNextIntent
(
Intent
(
this
@PlaybackService
,
PlayerActivity
::
class
.
java
)
)
val
immutableFlag
=
if
(
Build
.
VERSION
.
SDK_INT
>=
23
)
FLAG_IMMUTABLE
else
0
val
immutableFlag
=
if
(
Build
.
VERSION
.
SDK_INT
>=
23
)
FLAG_IMMUTABLE
else
0
getPendingIntent
(
0
,
immutableFlag
or
FLAG_UPDATE_CURRENT
)
getPendingIntent
(
0
,
immutableFlag
or
FLAG_UPDATE_CURRENT
)
...
@@ -180,7 +237,39 @@ class PlaybackService : MediaLibraryService() {
...
@@ -180,7 +237,39 @@ class PlaybackService : MediaLibraryService() {
mediaLibrarySession
=
mediaLibrarySession
=
MediaLibrarySession
.
Builder
(
this
,
player
,
librarySessionCallback
)
MediaLibrarySession
.
Builder
(
this
,
player
,
librarySessionCallback
)
.
setMediaItemFiller
(
CustomMediaItemFiller
())
.
setMediaItemFiller
(
CustomMediaItemFiller
())
.
setSessionActivity
(
p
endingIntent
)
.
setSessionActivity
(
sessionActivityP
endingIntent
)
.
build
()
.
build
()
if
(!
customLayout
.
isEmpty
())
{
// Send custom layout to legacy session.
mediaLibrarySession
.
setCustomLayout
(
customLayout
)
}
}
private
fun
getShuffleCommandButton
(
sessionCommand
:
SessionCommand
):
CommandButton
{
val
isOn
=
sessionCommand
.
customAction
==
CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON
return
CommandButton
.
Builder
()
.
setDisplayName
(
getString
(
if
(
isOn
)
R
.
string
.
exo_controls_shuffle_on_description
else
R
.
string
.
exo_controls_shuffle_off_description
)
)
.
setSessionCommand
(
sessionCommand
)
.
setIconResId
(
if
(
isOn
)
R
.
drawable
.
exo_icon_shuffle_off
else
R
.
drawable
.
exo_icon_shuffle_on
)
.
build
()
}
private
fun
ignoreFuture
(
customLayout
:
ListenableFuture
<
SessionResult
>)
{
/* Do nothing. */
}
private
class
CustomMediaItemFiller
:
MediaSession
.
MediaItemFiller
{
override
fun
fillInLocalConfiguration
(
session
:
MediaSession
,
controller
:
ControllerInfo
,
mediaItem
:
MediaItem
):
MediaItem
{
return
MediaItemTree
.
getItem
(
mediaItem
.
mediaId
)
?:
mediaItem
}
}
}
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment