playing in background
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
</manifest>
|
||||
|
||||
@@ -69,6 +69,7 @@ class OralTrainerController internal constructor(
|
||||
)
|
||||
player = ExoPlayer.Builder(appContext)
|
||||
.setMediaSourceFactory(mediaSourceFactory)
|
||||
.setWakeMode(C.WAKE_MODE_NETWORK)
|
||||
.build()
|
||||
.also { exoPlayer ->
|
||||
exoPlayer.repeatMode = Player.REPEAT_MODE_ALL
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
@@ -19,6 +22,11 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name=".TrainingPlaybackService"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="mediaPlayback" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
|
||||
@@ -44,11 +44,16 @@ class MainActivity : Activity() {
|
||||
private var translationRequest: CancellableTranslationRequest? = null
|
||||
|
||||
private var progressBarDragging = false
|
||||
private var trainingPlaybackServiceActive = false
|
||||
|
||||
private val screenActionReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
if (intent?.action == Intent.ACTION_SCREEN_OFF) {
|
||||
testSession.onScreenOff()
|
||||
// Training playback intentionally keeps running with the screen off.
|
||||
// The test module still pauses because recording/assessment state is UI-dependent.
|
||||
if (screenState.activeModule == Module.TEST) {
|
||||
testSession.onScreenOff()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -149,8 +154,9 @@ class MainActivity : Activity() {
|
||||
if (bindings.hasPlayerChrome) {
|
||||
bindings.statusText.text = "已停止配音播放"
|
||||
}
|
||||
} else {
|
||||
} else if (screenState.activeModule != Module.TRAIN) {
|
||||
controller.pause()
|
||||
stopTrainingPlaybackService()
|
||||
}
|
||||
super.onPause()
|
||||
}
|
||||
@@ -178,6 +184,7 @@ class MainActivity : Activity() {
|
||||
override fun onDestroy() {
|
||||
authCoordinator.reset()
|
||||
testSession.release()
|
||||
stopTrainingPlaybackService()
|
||||
controller.release()
|
||||
explanationOverlay.release()
|
||||
translationRequest?.cancel()
|
||||
@@ -221,6 +228,7 @@ class MainActivity : Activity() {
|
||||
sdk.config.userToken = null
|
||||
screenState.clearCourse()
|
||||
controller.pause()
|
||||
stopTrainingPlaybackService()
|
||||
screenState.activeModule = Module.COURSE
|
||||
setRootView()
|
||||
}
|
||||
@@ -230,6 +238,10 @@ class MainActivity : Activity() {
|
||||
testSession.stopDubbingIfNeeded()
|
||||
}
|
||||
screenState.activeModule = module
|
||||
if (module != Module.TRAIN) {
|
||||
controller.pause()
|
||||
stopTrainingPlaybackService()
|
||||
}
|
||||
if (module == Module.COURSE || module == Module.MINE) {
|
||||
catalogController.loadUserData(module) {
|
||||
screenFactory.refreshMyContent(sdk.userApi, authSessionStore.session?.token.orEmpty())
|
||||
@@ -305,6 +317,11 @@ class MainActivity : Activity() {
|
||||
|
||||
private fun applyPlaybackSnapshot(snapshot: PlaybackSnapshot) {
|
||||
if (!bindings.hasPlayerChrome) return
|
||||
if (screenState.activeModule == Module.TRAIN && snapshot.isPlaying) {
|
||||
startTrainingPlaybackService()
|
||||
} else if (!snapshot.isPlaying) {
|
||||
stopTrainingPlaybackService()
|
||||
}
|
||||
if (!progressBarDragging) {
|
||||
bindings.timeText.text =
|
||||
"${PlaybackFormat.time(snapshot.positionMs)} / ${PlaybackFormat.time(snapshot.durationMs)}"
|
||||
@@ -314,6 +331,18 @@ class MainActivity : Activity() {
|
||||
bindings.statusText.text = PlaybackFormat.status(snapshot)
|
||||
}
|
||||
|
||||
private fun startTrainingPlaybackService() {
|
||||
if (trainingPlaybackServiceActive) return
|
||||
TrainingPlaybackService.start(this)
|
||||
trainingPlaybackServiceActive = true
|
||||
}
|
||||
|
||||
private fun stopTrainingPlaybackService() {
|
||||
if (!trainingPlaybackServiceActive) return
|
||||
TrainingPlaybackService.stop(this)
|
||||
trainingPlaybackServiceActive = false
|
||||
}
|
||||
|
||||
private fun applySentence(sentence: SentenceBoundary?) {
|
||||
if (!bindings.hasPlayerChrome) return
|
||||
if (sentence == null) {
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
package cn.learningpad.oraltrainer.sample
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ServiceInfo
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
|
||||
/** Keeps the training player alive after the screen turns off or the app backgrounds. */
|
||||
class TrainingPlaybackService : Service() {
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? = null
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
||||
manager.createNotificationChannel(
|
||||
NotificationChannel(
|
||||
CHANNEL_ID,
|
||||
getString(R.string.training_playback_channel_name),
|
||||
NotificationManager.IMPORTANCE_LOW,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
val notification = buildNotification()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
startForeground(
|
||||
NOTIFICATION_ID,
|
||||
notification,
|
||||
ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK,
|
||||
)
|
||||
} else {
|
||||
startForeground(NOTIFICATION_ID, notification)
|
||||
}
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
private fun buildNotification(): Notification {
|
||||
val contentIntent = PendingIntent.getActivity(
|
||||
this,
|
||||
0,
|
||||
Intent(this, MainActivity::class.java).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
|
||||
PendingIntent.FLAG_IMMUTABLE,
|
||||
)
|
||||
return Notification.Builder(this, CHANNEL_ID)
|
||||
.setContentTitle(getString(R.string.training_playback_notification_title))
|
||||
.setContentText(getString(R.string.training_playback_notification_text))
|
||||
.setSmallIcon(R.drawable.ic_notification_play)
|
||||
.setContentIntent(contentIntent)
|
||||
.setOngoing(true)
|
||||
.setCategory(Notification.CATEGORY_TRANSPORT)
|
||||
.build()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val CHANNEL_ID = "training_playback"
|
||||
private const val NOTIFICATION_ID = 1001
|
||||
|
||||
fun start(context: Context) {
|
||||
val intent = Intent(context, TrainingPlaybackService::class.java)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
context.startForegroundService(intent)
|
||||
} else {
|
||||
context.startService(intent)
|
||||
}
|
||||
}
|
||||
|
||||
fun stop(context: Context) {
|
||||
context.stopService(Intent(context, TrainingPlaybackService::class.java))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M8,5v14l11,-7z" />
|
||||
</vector>
|
||||
@@ -1,4 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">八哥口语</string>
|
||||
<string name="training_playback_channel_name">训练播放</string>
|
||||
<string name="training_playback_notification_title">口语训练</string>
|
||||
<string name="training_playback_notification_text">正在后台播放</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
<resources>
|
||||
<string name="app_name">MynaTalk</string>
|
||||
<string name="training_playback_channel_name">Training playback</string>
|
||||
<string name="training_playback_notification_title">Oral training</string>
|
||||
<string name="training_playback_notification_text">Playing in the background</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user