add android support
This commit is contained in:
40
android/sample-app/build.gradle.kts
Normal file
40
android/sample-app/build.gradle.kts
Normal file
@@ -0,0 +1,40 @@
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "cn.learningpad.oraltrainer.sample"
|
||||
compileSdk {
|
||||
version = release(36) {
|
||||
minorApiLevel = 1
|
||||
}
|
||||
}
|
||||
buildToolsVersion = "36.1.0"
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "cn.learningpad.oraltrainer.sample"
|
||||
minSdk = 26
|
||||
targetSdk = 35
|
||||
versionCode = 1
|
||||
versionName = "0.1.0"
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
jvmTarget.set(JvmTarget.JVM_17)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":oral-trainer-sdk"))
|
||||
implementation("androidx.media3:media3-common:1.11.0")
|
||||
}
|
||||
19
android/sample-app/src/main/AndroidManifest.xml
Normal file
19
android/sample-app/src/main/AndroidManifest.xml
Normal 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>
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
9
android/sample-app/src/main/res/values/styles.xml
Normal file
9
android/sample-app/src/main/res/values/styles.xml
Normal 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>
|
||||
Reference in New Issue
Block a user