0. 辅导四简介:多媒体播放器
🤩:谢谢你们,谢谢我的坚持,都到辅导四了,咱们都是吃饱了撑着的哥们。
路人甲👩🎤:谁跟你是哥们?姐手抖进来这。
😃:FreeDesktop 网主说着是个多媒体播放器,准备好网址没有?放电影啦!
🏗️ 1. Common Module 共用仓库
对比了辅导三和辅导四,有些文件是共通的。不要再费时费力,又抄又翻,手指都打疼了。直接来个 common module —— 大家一起分享。
File => New => New Module:
Android => Module name: common
✏️, 改 common 的 gradle
打开 common 的 gradle:
在 dependencies 内:
api "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
api "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
api 'androidx.core:core-ktx:1.3.2'
api 'androidx.appcompat:appcompat:1.2.0'
// test
api 'junit:junit:4.13.1'
api 'androidx.test.ext:junit:1.1.2'
api 'androidx.test.espresso:espresso-core:3.3.0'
将所有的开头改成 api ,sync。😋:看!想都不用想,懒人最爱!
…
🕵️, 加自己的捷径包
在 Java 加个 helper 的 package 包裹,把 Log,Toast 的私货塞进去。
LogHelper:
const val TAG = "MTAG"
fun lgd(s:String) = Log.d(TAG, s)
fun lgi(s:String) = Log.i(TAG, s)
fun lge(s:String) = Log.e(TAG, s)
fun lgw(s:String) = Log.w(TAG, s)
fun lgv(s:String) = Log.v(TAG, s)
ToastHelper:
// Toast: len: 0-short, 1-long
fun msg(context: Context, s: String, len: Int) =
if (len > 0) Toast.makeText(context, s, LENGTH_LONG).show()
else Toast.makeText(context, s, LENGTH_SHORT).show()
…
📦,GStreamer package 包裹
加 package 包裹:common => java => New => Package
选 main\java 。
继续抄:
让 assets 和 GStreamer.java 驻新家。
…
《✔️》改 辅导一
Gradle: 跳到 dependencies
😋:换成一行的,够短了吧? Sync。
Tutorial1.kt:看看私货能用否?
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
try {
GStreamer.init(this)
} catch (e: Exception) {
msg(this, e.message.toString(), 1)
finish()
return
}
setContentView(R.layout.main)
val tv = findViewById<View>(R.id.textview_info) as TextView
tv.text = nativeGetGStreamerInfo() + " !"
}
把 Toast() 改成 msg() 。
再将 GStreamer 和 assets 删掉,
import 回来,
跑一次。😁:一样。
Android Studio 自动把 GStreamer 和 assets 装回去了。以后装在新项目,也可以这样操作。
…
《✔️》改 辅导二的
Gradle:在 dependencies 缩水, sync。
Tutorial2.kt:将 lgd, lgi 的 import 删掉,再 import 一次。
跑步前进…一切正常。
…
《✔️》改 辅导三
Gradle:在 dependencies 缩水, sync。
Tutorial3.kt:将 lgd, lgi 的 import 删掉,再 import 一次。
🉑 辅导三,辅导四 和 辅导五 共用一个 GStreamerSurfaceView。因此,这个可以搬到 common 里面去:
删掉 辅导三 里面的 GStreamerSurfaceView。
在 res/layout/main.xml :
<你的路径.common.ui.GStreamerSurfaceView
android:id="@+id/surface_video"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|center_horizontal" />
抬头换成新的地址。象我就是
com.homan.huang.common.ui.GStreamerSurfaceView
跑啊…正常。
♋️ 2. 辅导四转 Kotlin
【✔️】Gradle 和 main.xml
Gradle:在 dependencies 缩水, sync。👌
main.xml: 转 “你的路径.common.ui.GStreamerSurfaceView”
到 Tutorial4 ,跑起来呦,没问题。
…
【✔️】Tutorial4.Java 转 Kotlin
老样子,Ctrl+Alt+Shift+k,Yes:
有三处爆红:
- Date(pos) 改为 Date(pos.toLong())
- Date(duration)改为 Date(duration.toLong())
- 照旧给 nativeClassInit() 加 @JvmStatic
再跑起来。没事。
音响 和 画面 看起来都不错。
3. 分析 Turtorial4
class Tutorial4 : Activity(), SurfaceHolder.Callback, OnSeekBarChangeListener {
🤔(一看开头就知道好多仔啊!):你哪来的?下蛋啊?
🐔???🐤🐥🐣
🐾 JNI 参数
// JNI
private external fun nativeInit() // Initialize native code, build pipeline, etc
private external fun nativeFinalize() // Destroy pipeline and shutdown native code
private external fun nativeSetUri(uri: String?) // Set the URI of the media to play
private external fun nativePlay() // Set pipeline to PLAYING
private external fun nativeSetPosition(milliseconds: Int) // Seek to the indicated position, in milliseconds
private external fun nativePause() // Set pipeline to PAUSED
private external fun nativeSurfaceInit(surface: Any) // A new surface is available
private external fun nativeSurfaceFinalize() // Surface about to be destroyed
private val native_custom_data // Native code will use this to keep private data
: Long = 0
private var is_playing_desired // Whether the user asked to go to PLAYING
= false
private var position // Current position, reported by native code
= 0
private var duration // Current clip duration, reported by native code
= 0
private var is_local_media // Whether this clip is stored locally or is being streamed
= false
private var desired_position // Position where the users wants to seek to
= 0
private var mediaUri // URI of the clip being played
: String? = null
private val defaultMediaUri = "https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.ogv"
- 有流量当然有 URI
- 放电影还要有 poistion 位置显示
- 播过了多长时间 duration
- 下面还有 资源判断 是 当地的还是网上的 is_local_media
- 用户选项有一个:跳档,desired_position
- 下一个 mediaUri,网源
- 最后一个 defaultMediaUri
📺 UI 触屏参数
// UI
val play:ImageButton by lazy {
findViewById(R.id.button_play) }
val pause:ImageButton by lazy {
findViewById(R.id.button_stop) }
val sb:SeekBar by lazy {
findViewById(R.id.seek_bar) }
val msgTV:TextView by lazy {
findViewById(R.id.textview_message) }
val timeTV:TextView by lazy {
findViewById(R.id.textview_time) }
val gsv:GStreamerSurfaceView by lazy {
findViewById(R.id.surface_video) }
…
🔨 onCreate()
// 搜索棍
sb.setOnSeekBarChangeListener(this)
- 搜索棍 seek_bar
// Retrieve our previous state, or initialize it to default values
if (savedInstanceState != null) {
is_playing_desired = savedInstanceState.getBoolean("playing")
position = savedInstanceState.getInt("position")
duration = savedInstanceState.getInt("duration")
mediaUri = savedInstanceState.getString("mediaUri")
lgi("GStreamer--Activity created with saved state:")
} else {
is_playing_desired = false
duration = 0
position = duration
mediaUri = defaultMediaUri
lgi("GStreamer--Activity created with no saved state:")
}
检查 onRestart() 或 onStart() 回调的记忆,🙄:防止机器痴呆。onSaveInstanceState() 保持播放器的资料。
override fun onSaveInstanceState(outState: Bundle) {
lgd("GStreamer--Saving state, playing:" + is_playing_desired + " position:" + position +
" duration: " + duration + " uri: " + mediaUri)
outState.putBoolean("playing", is_playing_desired)
outState.putInt("position", position)
outState.putInt("duration", duration)
outState.putString("mediaUri", mediaUri)
}
接着,
is_local_media = false
默认使用网络流量。
…
🛵onGStreamerInitialized()
private fun onGStreamerInitialized() {
...
// Restore previous playing state
setMediaUri()
nativeSetPosition(position)
...
}
多了两行。
- setMediaUri():
private fun setMediaUri() {
nativeSetUri(mediaUri)
is_local_media = mediaUri!!.startsWith("file://")
}
通知 C 网址。
…
📥 Implementation 插入的方程。
surfaceChanged(), surfaceCreated(), surfaceDestroyed() 你们都知道了。
📲 onMediaSizeChanged():
private fun onMediaSizeChanged(width: Int, height: Int) {
lgi("GStreamer--Media size changed to " + width + "x" + height)
gsv.media_width = width
gsv.media_height = height
runOnUiThread {
gsv.requestLayout() }
}
平放看看:
锁死了。
gsv.media_width = height
gsv.media_height = width
长宽掉转,成了:
👨🔧:这个播放器没法用,还是改回来吧。还有这些按钮都是非人类的,谁会摆在中间啊?
…
📏 播放搜索棍
🧙♂️:这个不及我的棒棒,只能提放推拉。
// The Seek Bar thumb has moved, either because the user dragged it or we have called setProgress()
override fun onProgressChanged(sb: SeekBar, progress: Int, fromUser: Boolean) {
if (fromUser == false) return
desired_position = progress
// If this is a local file, allow scrub seeking, this is, seek as soon as the slider is moved.
if (is_local_media) nativeSetPosition(desired_position)
updateTimeWidget()
}
// The user started dragging the Seek Bar thumb
override fun onStartTrackingTouch(sb: SeekBar) {
nativePause()
}
// The user released the Seek Bar thumb
override fun onStopTrackingTouch(sb: SeekBar) {
// If this is a remote file, scrub seeking is probably not going to work smoothly enough.
// Therefore, perform only the