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
import
androidx.media3.common.AudioAttributes
import
androidx.media3.common.MediaItem
import
androidx.media3.exoplayer.ExoPlayer
import
androidx.media3.session.CommandButton
import
androidx.media3.session.LibraryResult
import
androidx.media3.session.MediaLibraryService
import
androidx.media3.session.MediaSession
import
androidx.media3.session.MediaSession.ControllerInfo
import
androidx.media3.session.SessionCommand
import
androidx.media3.session.SessionResult
import
com.google.common.collect.ImmutableList
import
com.google.common.util.concurrent.Futures
import
com.google.common.util.concurrent.ListenableFuture
class
PlaybackService
:
MediaLibraryService
()
{
private
val
librarySessionCallback
=
CustomMediaLibrarySessionCallback
()
private
lateinit
var
player
:
ExoPlayer
private
lateinit
var
mediaLibrarySession
:
MediaLibrarySession
private
val
librarySessionCallback
=
CustomMediaLibrarySessionCallback
()
private
lateinit
var
customCommands
:
List
<
CommandButton
>
private
var
customLayout
=
ImmutableList
.
of
<
CommandButton
>()
companion
object
{
private
const
val
SEARCH_QUERY_PREFIX_COMPAT
=
"androidx://media3-session/playFromSearch"
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
{
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
(
session
:
MediaLibrarySession
,
browser
:
MediaSession
.
ControllerInfo
,
browser
:
ControllerInfo
,
params
:
LibraryParams
?
):
ListenableFuture
<
LibraryResult
<
MediaItem
>>
{
return
Futures
.
immediateFuture
(
LibraryResult
.
ofItem
(
MediaItemTree
.
getRootItem
(),
params
))
...
...
@@ -54,7 +139,7 @@ class PlaybackService : MediaLibraryService() {
override
fun
onGetItem
(
session
:
MediaLibrarySession
,
browser
:
MediaSession
.
ControllerInfo
,
browser
:
ControllerInfo
,
mediaId
:
String
):
ListenableFuture
<
LibraryResult
<
MediaItem
>>
{
val
item
=
...
...
@@ -65,9 +150,24 @@ class PlaybackService : MediaLibraryService() {
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
(
session
:
MediaLibrarySession
,
browser
:
MediaSession
.
ControllerInfo
,
browser
:
ControllerInfo
,
parentId
:
String
,
page
:
Int
,
pageSize
:
Int
,
...
...
@@ -82,24 +182,9 @@ class PlaybackService : MediaLibraryService() {
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
(
session
:
MediaSession
,
controller
:
MediaSession
.
ControllerInfo
,
controller
:
ControllerInfo
,
uri
:
Uri
,
extras
:
Bundle
):
Int
{
...
...
@@ -107,7 +192,7 @@ class PlaybackService : MediaLibraryService() {
if
(
uri
.
toString
().
startsWith
(
SEARCH_QUERY_PREFIX
)
||
uri
.
toString
().
startsWith
(
SEARCH_QUERY_PREFIX_COMPAT
)
)
{
va
r
searchQuery
=
va
l
searchQuery
=
uri
.
getQueryParameter
(
"query"
)
?:
return
SessionResult
.
RESULT_ERROR_NOT_SUPPORTED
setMediaItemFromSearchQuery
(
searchQuery
)
...
...
@@ -117,47 +202,22 @@ class PlaybackService : MediaLibraryService() {
}
}
override
fun
onSubscribe
(
session
:
MediaLibrarySession
,
browser
:
MediaSession
.
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
())
}
}
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
}
private
class
CustomMediaItemFiller
:
MediaSession
.
MediaItemFiller
{
override
fun
fillInLocalConfiguration
(
session
:
MediaSession
,
controller
:
MediaSession
.
ControllerInfo
,
mediaItem
:
MediaItem
):
MediaItem
{
return
MediaItemTree
.
getItem
(
mediaItem
.
mediaId
)
?:
mediaItem
val
item
=
MediaItemTree
.
getItemFromTitle
(
mediaTitle
)
?:
MediaItemTree
.
getRandomItem
()
player
.
setMediaItem
(
item
)
}
}
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
()
{
player
=
ExoPlayer
.
Builder
(
this
)
...
...
@@ -165,13 +225,10 @@ class PlaybackService : MediaLibraryService() {
.
build
()
MediaItemTree
.
initialize
(
assets
)
val
parentScreenIntent
=
Intent
(
this
,
MainActivity
::
class
.
java
)
val
intent
=
Intent
(
this
,
PlayerActivity
::
class
.
java
)
val
pendingIntent
=
val
sessionActivityPendingIntent
=
TaskStackBuilder
.
create
(
this
).
run
{
addNextIntent
(
parentScreenIntent
)
addNextIntent
(
intent
)
addNextIntent
(
Intent
(
this
@PlaybackService
,
MainActivity
::
class
.
java
)
)
addNextIntent
(
Intent
(
this
@PlaybackService
,
PlayerActivity
::
class
.
java
)
)
val
immutableFlag
=
if
(
Build
.
VERSION
.
SDK_INT
>=
23
)
FLAG_IMMUTABLE
else
0
getPendingIntent
(
0
,
immutableFlag
or
FLAG_UPDATE_CURRENT
)
...
...
@@ -180,7 +237,39 @@ class PlaybackService : MediaLibraryService() {
mediaLibrarySession
=
MediaLibrarySession
.
Builder
(
this
,
player
,
librarySessionCallback
)
.
setMediaItemFiller
(
CustomMediaItemFiller
())
.
setSessionActivity
(
p
endingIntent
)
.
setSessionActivity
(
sessionActivityP
endingIntent
)
.
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