add android support

This commit is contained in:
2026-08-14 18:08:29 +08:00
parent 3b0fd719d9
commit f8a6bf24e4
26 changed files with 1538 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:label="Oral Trainer Sample"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,156 @@
package cn.learningpad.oraltrainer.sample
import android.app.Activity
import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import android.view.Gravity
import android.view.ViewGroup
import android.widget.Button
import android.widget.LinearLayout
import android.widget.TextView
import androidx.media3.common.MimeTypes
import cn.learningpad.oraltrainer.sdk.GestureEvent
import cn.learningpad.oraltrainer.sdk.LoopMode
import cn.learningpad.oraltrainer.sdk.OralTrainerController
import cn.learningpad.oraltrainer.sdk.OralTrainerListener
import cn.learningpad.oraltrainer.sdk.OralTrainerPlayerView
import cn.learningpad.oraltrainer.sdk.OralTrainerSdk
import cn.learningpad.oraltrainer.sdk.PlaybackSnapshot
import cn.learningpad.oraltrainer.sdk.PlayerConfig
import cn.learningpad.oraltrainer.sdk.SentenceBoundary
import cn.learningpad.oraltrainer.sdk.TrainingMediaItem
class MainActivity : Activity() {
private lateinit var controller: OralTrainerController
private lateinit var statusText: TextView
private lateinit var sentenceText: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val sdk = OralTrainerSdk.init(this)
controller = sdk.createController(
playerConfig = PlayerConfig(
sentenceMode = true,
defaultSeekStepMs = 10_000L,
autoPlay = false,
)
)
controller.setLoopMode(LoopMode.ALL)
val playerView = OralTrainerPlayerView(this).apply {
bind(controller)
layoutParams = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
0,
1f,
)
}
statusText = TextView(this).apply {
setTextColor(Color.WHITE)
textSize = 14f
setPadding(24, 16, 24, 8)
}
sentenceText = TextView(this).apply {
setTextColor(Color.WHITE)
textSize = 18f
setPadding(24, 4, 24, 16)
}
val controls = LinearLayout(this).apply {
orientation = LinearLayout.HORIZONTAL
gravity = Gravity.CENTER
setPadding(16, 8, 16, 20)
addView(commandButton("快退") { controller.rewind() })
addView(commandButton("播放/暂停") { controller.togglePlayPause() })
addView(commandButton("快进") { controller.fastForward() })
addView(commandButton("上一句") { controller.seekToPreviousSentence() })
addView(commandButton("下一句") { controller.seekToNextSentence() })
}
val root = LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
setBackgroundColor(Color.rgb(18, 18, 18))
addView(playerView)
addView(statusText)
addView(sentenceText)
addView(controls)
}
setContentView(root)
controller.addListener(object : OralTrainerListener {
override fun onPlaybackSnapshot(snapshot: PlaybackSnapshot) {
statusText.text = buildString {
append(if (snapshot.isPlaying) "播放中" else "已暂停")
append(" ")
append(format(snapshot.positionMs))
append(" / ")
append(format(snapshot.durationMs))
append(" 速度 ")
append(snapshot.playbackSpeed)
append("x")
}
}
override fun onSentenceChanged(sentence: SentenceBoundary?) {
sentenceText.text = sentence?.let {
"${it.index + 1} 句:${it.text.orEmpty()}"
} ?: "当前没有句子边界"
}
override fun onGesture(event: GestureEvent) {
statusText.text = "手势:${event.kind} ${event.action ?: ""}"
}
})
controller.loadItem(sampleOnlineLesson())
}
override fun onDestroy() {
controller.release()
super.onDestroy()
}
private fun commandButton(label: String, action: () -> Unit): Button {
return Button(this).apply {
text = label
setOnClickListener { action() }
layoutParams = LinearLayout.LayoutParams(
0,
ViewGroup.LayoutParams.WRAP_CONTENT,
1f,
).apply {
marginStart = 4
marginEnd = 4
}
}
}
private fun sampleOnlineLesson(): TrainingMediaItem {
return TrainingMediaItem(
id = "online_sample_01",
title = "Online Sample Lesson",
uri = Uri.parse("https://storage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"),
mimeType = MimeTypes.VIDEO_MP4,
customCacheKey = "online_sample_01",
sentences = listOf(
SentenceBoundary(0, 0L, 3_000L, "Listen once, then imitate."),
SentenceBoundary(1, 3_000L, 7_000L, "Swipe left to go back."),
SentenceBoundary(2, 7_000L, 11_000L, "Swipe right to move forward."),
SentenceBoundary(3, 11_000L, 16_000L, "Tap the video area to pause or play."),
),
)
}
private fun format(ms: Long): String {
if (ms < 0) {
return "--:--"
}
val totalSeconds = ms / 1000
val minutes = totalSeconds / 60
val seconds = totalSeconds % 60
return "%02d:%02d".format(minutes, seconds)
}
}

View File

@@ -0,0 +1,9 @@
<resources>
<style name="AppTheme" parent="android:style/Theme.Material.NoActionBar">
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowFullscreen">false</item>
<item name="android:fontFamily">sans</item>
<item name="android:colorAccent">#2563EB</item>
</style>
</resources>