Ijk.kt 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. package top.kikt.ijkplayer
  2. /// create 2019/3/7 by cai
  3. import android.content.Context
  4. import android.graphics.Bitmap
  5. import android.media.AudioManager
  6. import android.net.Uri
  7. import android.util.Base64
  8. import io.flutter.plugin.common.MethodChannel
  9. import io.flutter.plugin.common.PluginRegistry
  10. import top.kikt.ijkplayer.entity.IjkOption
  11. import top.kikt.ijkplayer.entity.Info
  12. import tv.danmaku.ijk.media.player.IjkMediaPlayer
  13. import tv.danmaku.ijk.media.player.TextureMediaPlayer
  14. import java.io.ByteArrayOutputStream
  15. import java.io.File
  16. class Ijk(private val registry: PluginRegistry.Registrar, private val options: Map<String, Any>) {
  17. private val textureEntry = registry.textures().createSurfaceTexture()
  18. val id: Long
  19. get() = textureEntry.id()
  20. val mediaPlayer: IjkMediaPlayer = IjkMediaPlayer()
  21. private val textureMediaPlayer: TextureMediaPlayer
  22. private val methodChannel: MethodChannel = MethodChannel(registry.messenger(), "top.kikt/ijkplayer/$id")
  23. private val notifyChannel: NotifyChannel = NotifyChannel(registry, id, this)
  24. var degree = 0
  25. var isDisposed = false
  26. init {
  27. textureMediaPlayer = TextureMediaPlayer(mediaPlayer)
  28. configOptions()
  29. textureMediaPlayer.surfaceTexture = textureEntry.surfaceTexture()
  30. methodChannel.setMethodCallHandler { call, result ->
  31. if (isDisposed) {
  32. return@setMethodCallHandler
  33. }
  34. when (call?.method) {
  35. "setNetworkDataSource" -> {
  36. val uri = call.argument<String>("uri")
  37. val params = call.argument<Map<String, String>>("headers")
  38. if (uri == null) {
  39. handleSetUriResult(Exception("uri是必传参数"), result)
  40. return@setMethodCallHandler
  41. }
  42. setUri(uri, params) { throwable ->
  43. handleSetUriResult(throwable, result)
  44. }
  45. }
  46. "setAssetDataSource" -> {
  47. val name = call.argument<String>("name")
  48. val `package` = call.argument<String>("package")
  49. if (name != null) {
  50. setAssetUri(name, `package`) { throwable ->
  51. handleSetUriResult(throwable, result)
  52. }
  53. } else {
  54. handleSetUriResult(Exception("没有找到资源"), result)
  55. }
  56. }
  57. "setFileDataSource" -> {
  58. val path = call.argument<String>("path")
  59. if (path != null) {
  60. setUri("file://$path", hashMapOf()) { throwable ->
  61. handleSetUriResult(throwable, result)
  62. }
  63. }
  64. }
  65. "play" -> {
  66. play()
  67. result?.success(true)
  68. }
  69. "pause" -> {
  70. pause()
  71. result?.success(true)
  72. }
  73. "stop" -> {
  74. stop()
  75. result?.success(true)
  76. }
  77. "getInfo" -> {
  78. val info = getInfo()
  79. result?.success(info.toMap())
  80. }
  81. "seekTo" -> {
  82. val target = call.argument<Double>("target")
  83. if (target != null) {
  84. seekTo((target * 1000).toLong())
  85. }
  86. result?.success(true)
  87. }
  88. "setVolume" -> {
  89. val volume = call.argument<Int>("volume")
  90. setVolume(volume)
  91. result?.success(true)
  92. }
  93. "getVolume" -> {
  94. // result?.success(this.mediaPlayer.setVolume())
  95. }
  96. "screenShot" -> {
  97. val bytes = screenShot()
  98. result?.success(bytes)
  99. }
  100. "setSpeed" -> {
  101. val speed = call.arguments<Double>()
  102. mediaPlayer.setSpeed(speed.toFloat())
  103. }
  104. else -> {
  105. result?.notImplemented()
  106. }
  107. }
  108. }
  109. }
  110. private val appContext: Context
  111. get() = registry.activity().application
  112. private fun configOptions() {
  113. // see https://www.jianshu.com/p/843c86a9e9ad
  114. mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "fflags", "fastseek")
  115. // 以下注释有选项会导致m3u8类型无声音
  116. // mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "analyzemaxduration", 100L)
  117. // mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "analyzeduration", 1)
  118. // mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "probesize", 1024 * 10)
  119. // mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "flush_packets", 1L)
  120. mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "reconnect", 5)
  121. mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", 5)
  122. mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "enable-accurate-seek", 1)
  123. mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 1) // 开硬解
  124. mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", 1)
  125. // mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max-buffer-size", maxCacheSize)
  126. // mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", if (isBufferCache) 1 else 0)
  127. fun setOptionToMediaPlayer(option: IjkOption) {
  128. if (option.isInit.not()) {
  129. return
  130. }
  131. val category = when (option.type) {
  132. IjkOption.Type.Format -> IjkMediaPlayer.OPT_CATEGORY_FORMAT
  133. IjkOption.Type.Player -> IjkMediaPlayer.OPT_CATEGORY_PLAYER
  134. IjkOption.Type.Sws -> IjkMediaPlayer.OPT_CATEGORY_SWS
  135. IjkOption.Type.Codec -> IjkMediaPlayer.OPT_CATEGORY_CODEC
  136. else -> -1
  137. }
  138. if (category == -1) {
  139. return
  140. }
  141. val value = option.value
  142. when (value) {
  143. is Int -> mediaPlayer.setOption(category, option.key, value.toLong())
  144. is String -> mediaPlayer.setOption(category, option.key, value)
  145. is Long -> mediaPlayer.setOption(category, option.key, value)
  146. }
  147. }
  148. val options = options["options"]
  149. if (options is List<*>) {
  150. for (option in options) {
  151. if (option is Map<*, *>) {
  152. val ijkOptions = IjkOption(option)
  153. setOptionToMediaPlayer(ijkOptions)
  154. }
  155. }
  156. }
  157. }
  158. private fun screenShot(): ByteArray? {
  159. val frameBitmap = mediaPlayer.frameBitmap
  160. return if (frameBitmap != null) {
  161. val outputStream = ByteArrayOutputStream()
  162. frameBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
  163. frameBitmap.recycle()
  164. outputStream.toByteArray()
  165. } else {
  166. null
  167. }
  168. }
  169. fun getInfo(): Info {
  170. val duration = mediaPlayer.duration
  171. val currentPosition = mediaPlayer.currentPosition
  172. val width = mediaPlayer.videoWidth
  173. val height = mediaPlayer.videoHeight
  174. val outputFps = mediaPlayer.videoOutputFramesPerSecond
  175. // mediaPlayer.mediaInfo.mAudioDecoder
  176. // mediaPlayer.mediaInfo.mVideoDecoder
  177. return Info(
  178. duration = duration.toDouble() / 1000,
  179. currentPosition = currentPosition.toDouble() / 1000,
  180. width = width,
  181. height = height,
  182. isPlaying = textureMediaPlayer.isPlaying,
  183. degree = degree,
  184. tcpSpeed = mediaPlayer.tcpSpeed,
  185. outputFps = outputFps
  186. // decodeFps = mediaPlayer.videoDecodeFramesPerSecond
  187. )
  188. }
  189. private fun handleSetUriResult(throwable: Throwable?, result: MethodChannel.Result?) {
  190. if (throwable == null) {
  191. result?.success(true)
  192. } else {
  193. throwable.printStackTrace()
  194. result?.error("1", "set resource error", throwable)
  195. }
  196. }
  197. private fun setUri(uriString: String, headers: Map<String, String>?, callback: (Throwable?) -> Unit) {
  198. try {
  199. val uri = Uri.parse(uriString)
  200. // val scheme = uri.scheme
  201. // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
  202. // (TextUtils.isEmpty(scheme) || scheme.equals("file", ignoreCase = true))) {
  203. // val dataSource = FileMediaDataSource(File(uri.toString()))
  204. // mediaPlayer.setDataSource(dataSource)
  205. // } else {
  206. mediaPlayer.setDataSource(appContext, uri, headers)
  207. // }
  208. mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC)
  209. mediaPlayer.prepareAsync()
  210. callback(null)
  211. } catch (e: Exception) {
  212. e.printStackTrace()
  213. callback(e)
  214. }
  215. }
  216. private fun setAssetUri(name: String, `package`: String?, callback: (Throwable?) -> Unit) {
  217. try {
  218. mediaPlayer.setOnPreparedListener {
  219. callback(null)
  220. }
  221. val asset =
  222. if (`package` == null) {
  223. registry.lookupKeyForAsset(name)
  224. } else {
  225. registry.lookupKeyForAsset(name, `package`)
  226. }
  227. val assetManager = registry.context().assets
  228. val input = assetManager.open(asset)
  229. val cacheDir = registry.context().cacheDir.absoluteFile.path
  230. val fileName = Base64.encodeToString(asset.toByteArray(), Base64.DEFAULT)
  231. val file = File(cacheDir, fileName)
  232. file.outputStream().use { outputStream ->
  233. input.use { inputStream ->
  234. inputStream.copyTo(outputStream)
  235. }
  236. }
  237. mediaPlayer.setDataSource(FileMediaDataSource(file))
  238. mediaPlayer.prepareAsync()
  239. } catch (e: Exception) {
  240. e.printStackTrace()
  241. callback(e)
  242. }
  243. }
  244. fun dispose() {
  245. if (isDisposed) {
  246. return
  247. }
  248. isDisposed = true
  249. tryCatchError {
  250. notifyChannel.dispose()
  251. methodChannel.setMethodCallHandler(null)
  252. textureMediaPlayer.stop()
  253. }
  254. tryCatchError {
  255. textureMediaPlayer.release()
  256. }
  257. tryCatchError {
  258. textureEntry.release()
  259. }
  260. }
  261. private fun tryCatchError(runnable: () -> Unit) {
  262. try {
  263. runnable()
  264. } catch (e: Exception) {
  265. e.printStackTrace()
  266. }
  267. }
  268. private fun play() {
  269. try {
  270. mediaPlayer.start()
  271. } catch (e: Exception) {
  272. e.printStackTrace()
  273. }
  274. }
  275. private fun pause() {
  276. textureMediaPlayer.pause()
  277. }
  278. private fun stop() {
  279. textureMediaPlayer.stop()
  280. }
  281. private fun seekTo(msec: Long) {
  282. textureMediaPlayer.seekTo(msec)
  283. }
  284. private fun setVolume(volume: Int?) {
  285. if (volume == null) {
  286. return
  287. }
  288. val v = volume.toFloat() / 100
  289. mediaPlayer.setVolume(v, v)
  290. }
  291. }