ym88659208ym87991671
Управление плеером с помощью голосовых команд | Документация для разработчиков

Управление плеером с помощью голосовых команд

Обновлено 9 ноября 2023

Умные устройства Sber поддерживают следующие команды управления воспроизведением аудио/видео:

  • «Продолжить»;

  • «Повторить»;

  • «Следующий/Предыдущий трек»;

  • «Случайный трек»;

  • «Остановить/Пауза»;

  • «Играть сначала»;

  • Команды перемотки:

    • «Перемотай назад/вперед на n секунд/минут/часов»;

    • «Перемотай»;

      Перематывает вперед на 15 секунд.

    • «Перемотай на n».

      Перематывает вперед на n минут.

Когда пользователь произносит одну из команд, операционная система устройства автоматически определяет приложение, к которому относится команда, и запрашивает у него информацию о состоянии плеера. После обработки состояния, операционная система устройства передает команду в приложение. Приложение изменяет состояние плеера соответствующим образом, например, останавливает плеер или перематывает трек.

Этот процесс можно представить так:

Обязательный текст

Таким образом, для поддержки голосового управления плеером, приложение должно:

Чтобы поддержать эту функциональность реализуйте инструменты Native App SDK.

Пример работы с голосовым управлением плеером

Рассмотрим как добавить поддержку голосового управления плеером в Android-приложение, созданное на основе стандартного шаблона Android Studio. Используя средства Native App SDK приложение передает состояние плеера в ОС и пишет логи при получении от ОС голосовых команд.

Пример MainActivity
package com.example.playercommanddemo

import android.os.Bundle
import android.util.Log
import com.google.android.material.snackbar.Snackbar
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.navigateUp
import androidx.navigation.ui.setupActionBarWithNavController
import android.view.Menu
import android.view.MenuItem
import com.example.playercommanddemo.PlayerCommandEnum.*
import com.example.playercommanddemo.databinding.ActivityMainBinding
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import ru.sberdevices.messaging.MessageId
import ru.sberdevices.messaging.Messaging
import ru.sberdevices.messaging.MessagingFactory
import ru.sberdevices.messaging.Payload
import ru.sberdevices.services.appstate.AppStateManagerFactory
import ru.sberdevices.services.appstate.AppStateProvider
import java.util.Date

class MainActivity : AppCompatActivity(), AppStateProvider {

private lateinit var appBarConfiguration: AppBarConfiguration
private lateinit var binding: ActivityMainBinding

// Player commands

private val commandParser = Json {
ignoreUnknownKeys = true
isLenient = true
coerceInputValues = true
}
private var isPlaying = true
private var playingPosition = 10.0f
private val playingDuration = 100

private val listener = object : Messaging.Listener {
override fun onError(messageId: MessageId, throwable: Throwable) {
Log.d("DEMOAPP", "Got error ${throwable.message}")
}

override fun onMessage(messageId: MessageId, payload: Payload) {
Log.d("DEMOAPP", "Got ${payload.data}")

val playerCommand = commandParser.decodeFromString<PlayerCommand>(payload.data)
Log.d("DEMOAPP", "Got $playerCommand")

when (playerCommand.command) {
PLAYER_STOP -> {
isPlaying = false
Log.d("DEMOAPP", "Stopping play")
}
PLAYER_CONTINUE -> {
isPlaying = true
Log.d("DEMOAPP", "Resuming play")
}
PLAYER_NEXT -> {
Log.d("DEMOAPP", "Next track")
}
PLAYER_PREV -> {
Log.d("DEMOAPP", "Previous track")
}
PLAYER_TO_START -> {
Log.d("DEMOAPP", "Go to start")
playingPosition = 0f
}
PLAYER_REWIND -> {
Log.d("DEMOAPP", "Rewinding to position ${playerCommand.position}")
playerCommand.position?.let {
playingPosition = it
}
}
null -> {
Log.d("DEMOAPP", "Got unexpected command")
}
}
}
}

override fun getState(): String {
val state = Json.encodeToString(
AppState(
PlayerAppState(
playing = isPlaying,
duration = playingDuration,
position = playingPosition,
stateChangedTimestamp = Date().time,
type = MediaType.video,
live = false
)
)
)
Log.d("DEMOAPP", "State requested from app. Will give state: $state")
return state
}

private fun initSberSdk() {
val messaging = MessagingFactory.create(this)
messaging.addListener(listener)

// AppStateManager можно создавать только в единственном экземпляре на одно приложение
val requestManager = AppStateManagerFactory.createRequestManager(this)
// помимо activity можно использовать любой объект
requestManager.setProvider(this)
}

//////////////////// Default code ///////////////////

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

initSberSdk()

binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)

setSupportActionBar(binding.toolbar)

val navController = findNavController(R.id.nav_host_fragment_content_main)
appBarConfiguration = AppBarConfiguration(navController.graph)
setupActionBarWithNavController(navController, appBarConfiguration)

binding.fab.setOnClickListener { view ->
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show()
}
}

override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.menu_main, menu)
return true
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
return when (item.itemId) {
R.id.action_settings -> true
else -> super.onOptionsItemSelected(item)
}
}

override fun onSupportNavigateUp(): Boolean {
val navController = findNavController(R.id.nav_host_fragment_content_main)
return navController.navigateUp(appBarConfiguration)
|| super.onSupportNavigateUp()
}


}

Подключение и инициализация Native App SDK

Так же как и другие зависимости, SDK подключается в разделе dependencies файла app/build.gradle:

dependencies {
implementation 'ru.sberdevices.smartapp:sdk:1.0.1'
...
}

Инициализируйте SDK в подходящем месте, например, в MainActivity:

AppStateManager можно создавать только в единственном экземпляре на одно приложение.

private fun initSberSdk() {
val messaging = MessagingFactory.create(this)
messaging.addListener(listener)

val requestManager = AppStateManagerFactory.createRequestManager(this)

// помимо activity можно использовать любой объект
requestManager.setProvider(this)
}

Передача состояния плеера

Состояние плеера описывается согласно заданному формату.

Добавьте класс с описанием состояния плеера:

package com.example.playercommanddemo

import kotlinx.serialization.Serializable

@Serializable
data class AppState(
val player: PlayerAppState
)

@Serializable
data class PlayerAppState(
val playing: Boolean,
val type: MediaType,
val live: Boolean,
val duration: Int,
val position: Float,
val stateChangedTimestamp: Long
)

@Serializable
enum class MediaType {
audio, video
}

Для передачи состояния плеера при запросе используйте метод getState(), интерфейса AppStateProvider:

override fun getState(): String {
val state = Json.encodeToString(
AppState(
PlayerAppState(
playing = isPlaying,
duration = playingDuration,
position = playingPosition,
stateChangedTimestamp = Date().time,
type = MediaType.video,
live = false
)
)
)
Log.d("DEMOAPP", "State requested from app. Will give state: $state")
return state
}

Обработка команд управления плеером

Операционная система устройства возвращает команды в заданном формате.

Добавьте класс с перечислением команд, которые может передать ОС:

package com.example.playercommanddemo

import kotlinx.serialization.Serializable

/**
* @property position in seconds
* @property command [Entities.DirectiveId]
*/
@Serializable
data class PlayerCommand(
val command: PlayerCommandEnum? = null,
val position: Float? = null
)

enum class PlayerCommandEnum {
PLAYER_STOP,
PLAYER_CONTINUE,
PLAYER_NEXT,
PLAYER_PREV,
PLAYER_TO_START,
PLAYER_REWIND
}

Чтобы обработать команду управления плеером используйте метод onMessage() слушателя библиотеки Messaging:

private val listener = object : Messaging.Listener {
override fun onError(messageId: MessageId, throwable: Throwable) {
Log.d("DEMOAPP", "Got error ${throwable.message}")
}

override fun onMessage(messageId: MessageId, payload: Payload) {
Log.d("DEMOAPP", "Got ${payload.data}")

val playerCommand = commandParser.decodeFromString<PlayerCommand>(payload.data)
Log.d("DEMOAPP", "Got $playerCommand")

when (playerCommand.command) {
PLAYER_STOP -> {
isPlaying = false
Log.d("DEMOAPP", "Stopping play")
}
PLAYER_CONTINUE -> {
isPlaying = true
Log.d("DEMOAPP", "Resuming play")
}
PLAYER_NEXT -> {
Log.d("DEMOAPP", "Next track")
}
PLAYER_PREV -> {
Log.d("DEMOAPP", "Previous track")
}
PLAYER_TO_START -> {
Log.d("DEMOAPP", "Go to start")
playingPosition = 0f
}
PLAYER_REWIND -> {
Log.d("DEMOAPP", "Rewinding to position ${playerCommand.position}")
playerCommand.position?.let {
playingPosition = it
}
}
null -> {
Log.d("DEMOAPP", "Got unexpected command")
}
}
}
}

Описание формата состояния плеера

playing
required
boolean

Указывает на то воспроизводится контент в данный момент или нет

type
string
Enum: "video" "audio"

какого типа контент играет

live
boolean

Указывает на потоковую трансляцию. Определяет возможность перемотки

duration
number

Продолжительность контента в секундах. Не передается при потоковой трансляции (live == true)

position
number

Текущая позиция в секундах. Не передается при потоковой трансляции (live == true)

stateChangedTimestamp
number

Время последнего последнего изменения состояния плеера (поле playing) в миллисекундах:

  • если контент приосталовлен — содержит время с момента постановки на пазу;
  • если контент воспроизводится — время прошедшее с момента включения плеера.
{
  • "playing": true,
  • "type": "video",
  • "live": false,
  • "duration": 200,
  • "position": 100,
  • "stateChangedTimestamp": 1432233446145000
}

Описание формата команд управления плеером

type
string

Тип команды. Команда управления плеером на фронтенде смартапа

Value: "player_command"
any
{
  • "type": "player_command",
  • "player_command": {
    }
}

Поле position, содержит время, на которое нужно перемотать трек, и может передаваться только в команде PLAYER_REWIND.

ПАО Сбербанк использует cookie для персонализации сервисов и удобства пользователей.
Вы можете запретить сохранение cookie в настройках своего браузера.