diff --git a/README_TECH.md b/README_TECH.md index 8ca93ac9..fe1bb861 100644 --- a/README_TECH.md +++ b/README_TECH.md @@ -1,5 +1,7 @@ # Han1meViewer 技术相关 +> 抄是程序员进步的阶梯。 + ## 概括 本软件使用 MVVM 架构,Material 3 视觉风格,Jetpack 不用问肯定用,但未使用 Compose(有一说一不用 Compose @@ -154,6 +156,20 @@ override fun onBindViewHolder( ### CI 更新渠道 +#### 你可以学到 + +#### 关键文件 + +#### 解释 + +当你的软件拓展性比较高,但受限于题材内容或者单纯懒,不方便自建服务器去读取这些拓展文件。但你又希望能让用户通过其他渠道实时的获取到更新(比如好心人上传了拓展文件,我合并到主分支之后,几分钟后用户就可以获得更新,而不用我自己做包),但又不是所有人需要这些拓展功能(要是人家不愿用你那功能,又一会一个 Release,用户也会烦;你自己一会发一个包你也会烦)。所以能不能给用户提供两种渠道?一个是稳定更新渠道,自己发版本;另一个是开发版,GitHub 自动构建,保证最新功能(最新拓展功能立即集成)但不保证稳定性。 + +答案是肯定的。其实我之前也不知道怎么做,但是 @NekoOuO 给我发了 [Foolbar/EhViewer](https://github.com/FooIbar/EhViewer/) 的做法,我想都没想就抄过来了。但没人详细教怎么做,我今天就来讲讲。 + +**先去看** GitHub CI 基础用法。 + +谷歌、掘金上全是教程。你先去查一查用法然后配置一下,刚开始的要求不多,你上传 commit 之后,GitHub CI 开始工作并成功 Build,就算入门了,先不用管 Build 之后干什么或者别的。如果你操作非常顺利,再看以下步骤。 + 待更... ### 共享关键H帧 diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 8c87577e..6975208f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -130,6 +130,7 @@ dependencies { // video implementation(libs.jiaozi.video.player) + implementation(libs.media3.exoplayer) // view @@ -149,6 +150,8 @@ dependencies { androidTestImplementation(libs.test.junit) androidTestImplementation(libs.test.espresso.core) + + debugImplementation(libs.leak.canary) } /** diff --git a/app/src/main/java/com/yenaly/han1meviewer/Preferences.kt b/app/src/main/java/com/yenaly/han1meviewer/Preferences.kt index 8b76bee7..759cc69c 100644 --- a/app/src/main/java/com/yenaly/han1meviewer/Preferences.kt +++ b/app/src/main/java/com/yenaly/han1meviewer/Preferences.kt @@ -7,7 +7,8 @@ import com.yenaly.han1meviewer.ui.fragment.settings.HKeyframeSettingsFragment import com.yenaly.han1meviewer.ui.fragment.settings.HomeSettingsFragment import com.yenaly.han1meviewer.ui.fragment.settings.NetworkSettingsFragment import com.yenaly.han1meviewer.ui.fragment.settings.PlayerSettingsFragment -import com.yenaly.han1meviewer.ui.view.HJzvdStd +import com.yenaly.han1meviewer.ui.view.video.HJzvdStd +import com.yenaly.han1meviewer.ui.view.video.HMediaKernel import com.yenaly.han1meviewer.util.CookieString import com.yenaly.yenaly_libs.utils.applicationContext import com.yenaly.yenaly_libs.utils.getSpValue @@ -64,6 +65,12 @@ object Preferences { // 設定 相關 + val switchPlayerKernel: String + get() = preferenceSp.getString( + PlayerSettingsFragment.SWITCH_PLAYER_KERNEL, + HMediaKernel.Type.ExoPlayer.name + ) ?: HMediaKernel.Type.ExoPlayer.name + val showBottomProgress: Boolean get() = preferenceSp.getBoolean( PlayerSettingsFragment.SHOW_BOTTOM_PROGRESS, diff --git a/app/src/main/java/com/yenaly/han1meviewer/ui/activity/VideoActivity.kt b/app/src/main/java/com/yenaly/han1meviewer/ui/activity/VideoActivity.kt index f8d2d721..f1164cc7 100644 --- a/app/src/main/java/com/yenaly/han1meviewer/ui/activity/VideoActivity.kt +++ b/app/src/main/java/com/yenaly/han1meviewer/ui/activity/VideoActivity.kt @@ -13,6 +13,7 @@ import cn.jzvd.JZDataSource import cn.jzvd.Jzvd import coil.load import com.yenaly.han1meviewer.COMMENT_TYPE +import com.yenaly.han1meviewer.Preferences import com.yenaly.han1meviewer.R import com.yenaly.han1meviewer.VIDEO_CODE import com.yenaly.han1meviewer.VIDEO_COMMENT_PREFIX @@ -24,6 +25,7 @@ import com.yenaly.han1meviewer.logic.exception.ParseException import com.yenaly.han1meviewer.logic.state.VideoLoadingState import com.yenaly.han1meviewer.ui.fragment.video.CommentFragment import com.yenaly.han1meviewer.ui.fragment.video.VideoIntroductionFragment +import com.yenaly.han1meviewer.ui.view.video.HMediaKernel import com.yenaly.han1meviewer.ui.viewmodel.CommentViewModel import com.yenaly.han1meviewer.ui.viewmodel.VideoViewModel import com.yenaly.han1meviewer.util.showAlertDialog @@ -50,6 +52,8 @@ class VideoActivity : YenalyActivity(), private val commentViewModel by viewModels() + private val kernel = HMediaKernel.Type.fromString(Preferences.switchPlayerKernel) + private val videoCode by intentExtra(VIDEO_CODE) private var videoTitle: String? = null private var videoCodeByWebsite: String? = null @@ -107,7 +111,7 @@ class VideoActivity : YenalyActivity(), } else { binding.videoPlayer.setUp( JZDataSource(state.info.videoUrls, state.info.title), - Jzvd.SCREEN_NORMAL + Jzvd.SCREEN_NORMAL, kernel ) } binding.videoPlayer.posterImageView.load(state.info.coverUrl) { diff --git a/app/src/main/java/com/yenaly/han1meviewer/ui/adapter/VideoSpeedAdapter.kt b/app/src/main/java/com/yenaly/han1meviewer/ui/adapter/VideoSpeedAdapter.kt index a93f05d4..ba0c6198 100644 --- a/app/src/main/java/com/yenaly/han1meviewer/ui/adapter/VideoSpeedAdapter.kt +++ b/app/src/main/java/com/yenaly/han1meviewer/ui/adapter/VideoSpeedAdapter.kt @@ -7,7 +7,7 @@ import android.view.ViewGroup import android.widget.TextView import com.chad.library.adapter4.BaseQuickAdapter import com.chad.library.adapter4.viewholder.QuickViewHolder -import com.yenaly.han1meviewer.ui.view.HJzvdStd +import com.yenaly.han1meviewer.ui.view.video.HJzvdStd /** * @project Han1meViewer diff --git a/app/src/main/java/com/yenaly/han1meviewer/ui/fragment/settings/HKeyframeSettingsFragment.kt b/app/src/main/java/com/yenaly/han1meviewer/ui/fragment/settings/HKeyframeSettingsFragment.kt index 848afc38..04f8f9e2 100644 --- a/app/src/main/java/com/yenaly/han1meviewer/ui/fragment/settings/HKeyframeSettingsFragment.kt +++ b/app/src/main/java/com/yenaly/han1meviewer/ui/fragment/settings/HKeyframeSettingsFragment.kt @@ -10,7 +10,7 @@ import androidx.preference.SwitchPreferenceCompat import com.yenaly.han1meviewer.R import com.yenaly.han1meviewer.ui.activity.SettingsActivity import com.yenaly.han1meviewer.ui.fragment.IToolbarFragment -import com.yenaly.han1meviewer.ui.view.HJzvdStd +import com.yenaly.han1meviewer.ui.view.video.HJzvdStd import com.yenaly.yenaly_libs.base.settings.YenalySettingsFragment /** diff --git a/app/src/main/java/com/yenaly/han1meviewer/ui/fragment/settings/HomeSettingsFragment.kt b/app/src/main/java/com/yenaly/han1meviewer/ui/fragment/settings/HomeSettingsFragment.kt index 54b6df59..8ed65f54 100644 --- a/app/src/main/java/com/yenaly/han1meviewer/ui/fragment/settings/HomeSettingsFragment.kt +++ b/app/src/main/java/com/yenaly/han1meviewer/ui/fragment/settings/HomeSettingsFragment.kt @@ -27,7 +27,7 @@ import com.yenaly.han1meviewer.logic.state.WebsiteState import com.yenaly.han1meviewer.ui.activity.AboutActivity import com.yenaly.han1meviewer.ui.activity.SettingsActivity import com.yenaly.han1meviewer.ui.fragment.IToolbarFragment -import com.yenaly.han1meviewer.ui.view.MaterialDialogPreference +import com.yenaly.han1meviewer.ui.view.pref.MaterialDialogPreference import com.yenaly.han1meviewer.ui.viewmodel.AppViewModel import com.yenaly.han1meviewer.util.hanimeVideoLocalFolder import com.yenaly.han1meviewer.util.showAlertDialog diff --git a/app/src/main/java/com/yenaly/han1meviewer/ui/fragment/settings/NetworkSettingsFragment.kt b/app/src/main/java/com/yenaly/han1meviewer/ui/fragment/settings/NetworkSettingsFragment.kt index 3cbd9a50..6174e0ea 100644 --- a/app/src/main/java/com/yenaly/han1meviewer/ui/fragment/settings/NetworkSettingsFragment.kt +++ b/app/src/main/java/com/yenaly/han1meviewer/ui/fragment/settings/NetworkSettingsFragment.kt @@ -23,7 +23,7 @@ import com.yenaly.han1meviewer.logic.network.HanimeNetwork import com.yenaly.han1meviewer.logout import com.yenaly.han1meviewer.ui.activity.SettingsActivity import com.yenaly.han1meviewer.ui.fragment.IToolbarFragment -import com.yenaly.han1meviewer.ui.view.MaterialDialogPreference +import com.yenaly.han1meviewer.ui.view.pref.MaterialDialogPreference import com.yenaly.han1meviewer.util.showAlertDialog import com.yenaly.yenaly_libs.ActivitiesManager import com.yenaly.yenaly_libs.base.settings.YenalySettingsFragment diff --git a/app/src/main/java/com/yenaly/han1meviewer/ui/fragment/settings/PlayerSettingsFragment.kt b/app/src/main/java/com/yenaly/han1meviewer/ui/fragment/settings/PlayerSettingsFragment.kt index 92e0f5b4..67414a82 100644 --- a/app/src/main/java/com/yenaly/han1meviewer/ui/fragment/settings/PlayerSettingsFragment.kt +++ b/app/src/main/java/com/yenaly/han1meviewer/ui/fragment/settings/PlayerSettingsFragment.kt @@ -7,8 +7,9 @@ import androidx.preference.SwitchPreferenceCompat import com.yenaly.han1meviewer.R import com.yenaly.han1meviewer.ui.activity.SettingsActivity import com.yenaly.han1meviewer.ui.fragment.IToolbarFragment -import com.yenaly.han1meviewer.ui.view.HJzvdStd -import com.yenaly.han1meviewer.ui.view.MaterialDialogPreference +import com.yenaly.han1meviewer.ui.view.pref.MaterialDialogPreference +import com.yenaly.han1meviewer.ui.view.video.HJzvdStd +import com.yenaly.han1meviewer.ui.view.video.HMediaKernel import com.yenaly.yenaly_libs.base.settings.YenalySettingsFragment /** @@ -20,12 +21,15 @@ class PlayerSettingsFragment : YenalySettingsFragment(R.xml.settings_player), IToolbarFragment { companion object { + const val SWITCH_PLAYER_KERNEL = "switch_player_kernel" const val SHOW_BOTTOM_PROGRESS = "show_bottom_progress" const val PLAYER_SPEED = "player_speed" const val SLIDE_SENSITIVITY = "slide_sensitivity" const val LONG_PRESS_SPEED_TIMES = "long_press_speed_times" } + private val switchPlayerKernel + by safePreference(SWITCH_PLAYER_KERNEL) private val showBottomProgressPref by safePreference(SHOW_BOTTOM_PROGRESS) private val playerSpeed @@ -41,6 +45,12 @@ class PlayerSettingsFragment : YenalySettingsFragment(R.xml.settings_player), } override fun onPreferencesCreated(savedInstanceState: Bundle?) { + switchPlayerKernel.apply { + val kernelNames = HMediaKernel.Type.entries.map { it.name }.toTypedArray() + entries = kernelNames + entryValues = kernelNames + if (value == null) setValueIndex(HMediaKernel.Type.ExoPlayer.ordinal) + } playerSpeed.apply { entries = HJzvdStd.speedStringArray entryValues = Array(HJzvdStd.speedArray.size) { diff --git a/app/src/main/java/com/yenaly/han1meviewer/ui/view/MaterialDialogPreference.kt b/app/src/main/java/com/yenaly/han1meviewer/ui/view/pref/MaterialDialogPreference.kt similarity index 95% rename from app/src/main/java/com/yenaly/han1meviewer/ui/view/MaterialDialogPreference.kt rename to app/src/main/java/com/yenaly/han1meviewer/ui/view/pref/MaterialDialogPreference.kt index 32ec3a78..e075dc49 100644 --- a/app/src/main/java/com/yenaly/han1meviewer/ui/view/MaterialDialogPreference.kt +++ b/app/src/main/java/com/yenaly/han1meviewer/ui/view/pref/MaterialDialogPreference.kt @@ -1,4 +1,4 @@ -package com.yenaly.han1meviewer.ui.view +package com.yenaly.han1meviewer.ui.view.pref import android.content.Context import android.util.AttributeSet diff --git a/app/src/main/java/com/yenaly/han1meviewer/ui/view/HJzvdStd.kt b/app/src/main/java/com/yenaly/han1meviewer/ui/view/video/HJzvdStd.kt similarity index 93% rename from app/src/main/java/com/yenaly/han1meviewer/ui/view/HJzvdStd.kt rename to app/src/main/java/com/yenaly/han1meviewer/ui/view/video/HJzvdStd.kt index 9131cd24..1e5940c0 100644 --- a/app/src/main/java/com/yenaly/han1meviewer/ui/view/HJzvdStd.kt +++ b/app/src/main/java/com/yenaly/han1meviewer/ui/view/video/HJzvdStd.kt @@ -1,12 +1,9 @@ -package com.yenaly.han1meviewer.ui.view +package com.yenaly.han1meviewer.ui.view.video import android.annotation.SuppressLint import android.content.Context -import android.content.pm.ActivityInfo import android.graphics.Typeface import android.media.AudioManager -import android.media.MediaPlayer -import android.media.PlaybackParams import android.provider.Settings import android.provider.Settings.SettingNotFoundException import android.util.AttributeSet @@ -30,9 +27,7 @@ import androidx.core.view.isVisible import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import cn.jzvd.JZDataSource -import cn.jzvd.JZMediaSystem import cn.jzvd.JZUtils -import cn.jzvd.Jzvd import cn.jzvd.JzvdStd import com.itxca.spannablex.spannable import com.yenaly.han1meviewer.Preferences @@ -46,6 +41,7 @@ import com.yenaly.han1meviewer.util.setStateViewLayout import com.yenaly.han1meviewer.util.showAlertDialog import com.yenaly.yenaly_libs.utils.OrientationManager import com.yenaly.yenaly_libs.utils.activity +import com.yenaly.yenaly_libs.utils.appScreenWidth import com.yenaly.yenaly_libs.utils.unsafeLazy import com.yenaly.yenaly_libs.utils.view.removeItself import java.util.Timer @@ -253,6 +249,7 @@ class HJzvdStd @JvmOverloads constructor( override fun init(context: Context?) { super.init(context) + SAVE_PROGRESS = false tvSpeed = findViewById(R.id.tv_speed) tvKeyframe = findViewById(R.id.tv_keyframe) tvTimer = findViewById(R.id.tv_timer) @@ -265,7 +262,15 @@ class HJzvdStd @JvmOverloads constructor( } override fun setUp(jzDataSource: JZDataSource?, screen: Int) { - super.setUp(jzDataSource, screen, HJZMediaSystem::class.java) + super.setUp(jzDataSource, screen, ExoMediaKernel::class.java) + } + + fun setUp(jzDataSource: JZDataSource?, screen: Int, kernel: HMediaKernel.Type) { + setUp(jzDataSource, screen, kernel.clazz) + } + + override fun setUp(jzDataSource: JZDataSource?, screen: Int, clazz: Class<*>) { + super.setUp(jzDataSource, screen, clazz) Log.d("CustomJzvdStd-Settings", buildString { append("showBottomProgress: ") appendLine(showBottomProgress) @@ -404,7 +409,7 @@ class HJzvdStd @JvmOverloads constructor( } } else { //如果y轴滑动距离超过设置的处理范围,那么进行滑动事件处理 - if (mDownX < mScreenHeight * 0.5f) { //左侧改变亮度 + if (mDownX < appScreenWidth * 0.5f) { //左侧改变亮度 mChangeBrightness = true val lp = JZUtils.getWindow(context).attributes if (lp.screenBrightness < 0) { @@ -637,36 +642,4 @@ class HJzvdStd @JvmOverloads constructor( else -> throw IllegalStateException("Invalid sensitivity value: $this") } } -} - -class HJZMediaSystem(jzvd: Jzvd) : JZMediaSystem(jzvd) { - - // #issue-26: 有的手機長按快進會報錯,合理懷疑是不是因爲沒有加 post - // #issue-28: 有的平板长按快进也会报错,结果是 IllegalArgumentException,很奇怪,两次 try-catch 处理试试。 - override fun setSpeed(speed: Float) { - mMediaHandler.post { - try { - val pp = mediaPlayer.playbackParams - pp.speed = speed.absoluteValue - mediaPlayer.playbackParams = pp - } catch (e: IllegalArgumentException) { - try { - val opp = PlaybackParams().setSpeed(speed.absoluteValue) - mediaPlayer.playbackParams = opp - } catch (e: IllegalArgumentException) { - e.printStackTrace() - } - } - } - } - - override fun onVideoSizeChanged(mediaPlayer: MediaPlayer?, width: Int, height: Int) { - super.onVideoSizeChanged(mediaPlayer, width, height) - val ratio = width.toFloat() / height // > 1 橫屏, < 1 竖屏 - if (ratio > 1) { - Jzvd.FULLSCREEN_ORIENTATION = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE - } else { - Jzvd.FULLSCREEN_ORIENTATION = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT - } - } } \ No newline at end of file diff --git a/app/src/main/java/com/yenaly/han1meviewer/ui/view/video/HMediaKernel.kt b/app/src/main/java/com/yenaly/han1meviewer/ui/view/video/HMediaKernel.kt new file mode 100644 index 00000000..de263314 --- /dev/null +++ b/app/src/main/java/com/yenaly/han1meviewer/ui/view/video/HMediaKernel.kt @@ -0,0 +1,335 @@ +package com.yenaly.han1meviewer.ui.view.video + +import android.content.pm.ActivityInfo +import android.graphics.SurfaceTexture +import android.media.MediaPlayer +import android.media.PlaybackParams +import android.os.Handler +import android.os.HandlerThread +import android.os.Looper +import android.util.Log +import android.view.Surface +import androidx.annotation.OptIn +import androidx.media3.common.MediaItem +import androidx.media3.common.PlaybackException +import androidx.media3.common.PlaybackParameters +import androidx.media3.common.Player +import androidx.media3.common.Timeline +import androidx.media3.common.VideoSize +import androidx.media3.common.util.UnstableApi +import androidx.media3.datasource.DefaultDataSource +import androidx.media3.datasource.DefaultHttpDataSource +import androidx.media3.exoplayer.DefaultLoadControl +import androidx.media3.exoplayer.DefaultRenderersFactory +import androidx.media3.exoplayer.ExoPlayer +import androidx.media3.exoplayer.LoadControl +import androidx.media3.exoplayer.source.ProgressiveMediaSource +import androidx.media3.exoplayer.trackselection.AdaptiveTrackSelection +import androidx.media3.exoplayer.trackselection.DefaultTrackSelector +import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter +import cn.jzvd.JZMediaInterface +import cn.jzvd.JZMediaSystem +import cn.jzvd.Jzvd +import kotlin.math.absoluteValue + + +/** + * @project Han1meViewer + * @author Yenaly Liew + * @time 2024/04/21 021 16:57 + */ +sealed interface HMediaKernel { + enum class Type(val clazz: Class) { + MediaPlayer(SystemMediaKernel::class.java), + ExoPlayer(ExoMediaKernel::class.java); + + companion object { + fun fromString(name: String): Type { + return when (name) { + MediaPlayer.name -> MediaPlayer + ExoPlayer.name -> ExoPlayer + else -> ExoPlayer + } + } + } + } +} + +class ExoMediaKernel(jzvd: Jzvd) : JZMediaInterface(jzvd), Player.Listener, HMediaKernel { + companion object { + const val TAG = "ExoMediaCore" + } + + private var _exoPlayer: ExoPlayer? = null + private val exoPlayer get() = _exoPlayer!! + + private var callback: Runnable? = null + private var prevSeek = 0L + + @OptIn(UnstableApi::class) + override fun prepare() { + Log.e(TAG, "prepare") + val context = jzvd.context + + release() + mMediaHandlerThread = HandlerThread("JZVD") + mMediaHandlerThread.start() + mMediaHandler = Handler(Looper.getMainLooper()) + handler = Handler(Looper.getMainLooper()) + mMediaHandler.post { + val videoTrackSelectionFactory = AdaptiveTrackSelection.Factory() + val trackSelector = DefaultTrackSelector(context, videoTrackSelectionFactory) + + val loadControl: LoadControl = DefaultLoadControl.Builder() + // .setBufferDurationsMs(360000, 600000, 1000, 5000) + // .setPrioritizeTimeOverSizeThresholds(false) + // .setTargetBufferBytes(C.LENGTH_UNSET) + .build() + + + val bandwidthMeter = DefaultBandwidthMeter.Builder(context).build() + // 2. Create the player + val renderersFactory = DefaultRenderersFactory(context) + _exoPlayer = ExoPlayer.Builder(context, renderersFactory) + .setTrackSelector(trackSelector) + .setLoadControl(loadControl) + .setBandwidthMeter(bandwidthMeter) + .build() + // Produces DataSource instances through which media data is loaded. + val dataSourceFactory = DefaultDataSource.Factory( + context, + DefaultHttpDataSource.Factory() + .setDefaultRequestProperties(jzvd.jzDataSource.headerMap) + ) + + val currUrl = jzvd.jzDataSource.currentUrl.toString() + val videoSource = if (currUrl.contains(".m3u8")) { + error("HLS is not supported") + } else { + ProgressiveMediaSource.Factory(dataSourceFactory) + .createMediaSource(MediaItem.fromUri(currUrl)) + } + + Log.e(TAG, "URL Link = $currUrl") + + exoPlayer.addListener(this) + + val isLoop = jzvd.jzDataSource.looping + if (isLoop) { + exoPlayer.repeatMode = Player.REPEAT_MODE_ONE + } else { + exoPlayer.repeatMode = Player.REPEAT_MODE_OFF + } + exoPlayer.setMediaSource(videoSource) + exoPlayer.prepare() + exoPlayer.playWhenReady = true + callback = OnBufferingUpdate() + + val surfaceTexture = jzvd.textureView?.surfaceTexture + surfaceTexture?.let { exoPlayer.setVideoSurface(Surface(it)) } + } + } + + override fun start() { + mMediaHandler.post { + exoPlayer.playWhenReady = true + } + } + + override fun onVideoSizeChanged(videoSize: VideoSize) { + val realWidth = videoSize.width * videoSize.pixelWidthHeightRatio + val realHeight = videoSize.height + handler.post { + jzvd.onVideoSizeChanged(realWidth.toInt(), realHeight) + } + val ratio = realWidth / realHeight // > 1 橫屏, < 1 竖屏 + if (ratio > 1) { + Jzvd.FULLSCREEN_ORIENTATION = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE + } else { + Jzvd.FULLSCREEN_ORIENTATION = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + } + } + + override fun onRenderedFirstFrame() { + Log.e(TAG, "onRenderedFirstFrame") + } + + override fun pause() { + mMediaHandler.post { + exoPlayer.playWhenReady = false + } + } + + override fun isPlaying(): Boolean { + return exoPlayer.playWhenReady + } + + override fun seekTo(time: Long) { + mMediaHandler.post { + if (time != prevSeek) { + if (time >= exoPlayer.bufferedPosition) { + jzvd.onStatePreparingPlaying() + } + exoPlayer.seekTo(time) + prevSeek = time + jzvd.seekToInAdvance = time + } + } + } + + override fun release() { + if (mMediaHandler != null && mMediaHandlerThread != null && _exoPlayer != null) { //不知道有没有妖孽 + val tmpHandlerThread = mMediaHandlerThread + val tmpMediaPlayer = exoPlayer + SAVED_SURFACE = null + mMediaHandler.post { + tmpMediaPlayer.release() //release就不能放到主线程里,界面会卡顿 + tmpHandlerThread.quit() + _exoPlayer = null + } + } + } + + override fun getCurrentPosition(): Long { + return exoPlayer.currentPosition + } + + override fun getDuration(): Long { + return exoPlayer.duration + } + + override fun setVolume(leftVolume: Float, rightVolume: Float) { + mMediaHandler.post { + exoPlayer.volume = (leftVolume + rightVolume) / 2 + } + } + + override fun setSpeed(speed: Float) { + mMediaHandler.post { + val playbackParams = PlaybackParameters(speed, 1.0F) + _exoPlayer?.playbackParameters = playbackParams + } + } + + override fun onTimelineChanged(timeline: Timeline, reason: Int) { + Log.e(TAG, "onTimelineChanged") + } + + override fun onIsLoadingChanged(isLoading: Boolean) { + Log.e(TAG, "onIsLoadingChanged") + } + + override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) { + if (playWhenReady && exoPlayer.playbackState == Player.STATE_READY) { + handler.post { + jzvd.onStatePlaying() + } + } + } + + override fun onPlaybackStateChanged(playbackState: Int) { + handler.post { + when (playbackState) { + Player.STATE_BUFFERING -> { + jzvd.onStatePreparingPlaying() + callback?.let(handler::post) + } + + Player.STATE_READY -> { + jzvd.onStatePlaying() + } + + Player.STATE_ENDED -> { + jzvd.onCompletion() + } + + else -> { + Log.e(TAG, "onPlaybackStateChanged: $playbackState") + } + } + } + } + + override fun onPlayerError(error: PlaybackException) { + Log.e(TAG, "onPlayerError: $error") + handler.post { jzvd.onError(1000, 1000) } + } + + override fun onPositionDiscontinuity( + oldPosition: Player.PositionInfo, + newPosition: Player.PositionInfo, + reason: Int, + ) { + if (reason == Player.DISCONTINUITY_REASON_SEEK) { + handler.post { jzvd.onSeekComplete() } + } + } + + override fun setSurface(surface: Surface?) { + exoPlayer.setVideoSurface(surface) + } + + override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) { + if (SAVED_SURFACE == null) { + SAVED_SURFACE = surface + prepare() + } else { + jzvd.textureView.setSurfaceTexture(SAVED_SURFACE) + } + } + + override fun onSurfaceTextureSizeChanged(st: SurfaceTexture, width: Int, height: Int) = Unit + + override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean = false + + override fun onSurfaceTextureUpdated(surface: SurfaceTexture) = Unit + + + private inner class OnBufferingUpdate : Runnable { + override fun run() { + _exoPlayer?.bufferedPercentage?.let { per -> + handler.post { + jzvd.setBufferProgress(per) + } + if (per < 100) { + handler.postDelayed(this, 300) + } else { + handler.removeCallbacks(this) + } + return + } + handler.removeCallbacks(this) + } + } +} + +class SystemMediaKernel(jzvd: Jzvd) : JZMediaSystem(jzvd), HMediaKernel { + // #issue-26: 有的手機長按快進會報錯,合理懷疑是不是因爲沒有加 post + // #issue-28: 有的平板长按快进也会报错,结果是 IllegalArgumentException,很奇怪,两次 try-catch 处理试试。 + override fun setSpeed(speed: Float) { + mMediaHandler.post { + try { + val pp = mediaPlayer.playbackParams + pp.speed = speed.absoluteValue + mediaPlayer.playbackParams = pp + } catch (e: IllegalArgumentException) { + try { + val opp = PlaybackParams().setSpeed(speed.absoluteValue) + mediaPlayer.playbackParams = opp + } catch (e: IllegalArgumentException) { + e.printStackTrace() + } + } + } + } + + override fun onVideoSizeChanged(mediaPlayer: MediaPlayer?, width: Int, height: Int) { + super.onVideoSizeChanged(mediaPlayer, width, height) + val ratio = width.toFloat() / height // > 1 橫屏, < 1 竖屏 + if (ratio > 1) { + Jzvd.FULLSCREEN_ORIENTATION = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE + } else { + Jzvd.FULLSCREEN_ORIENTATION = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + } + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_video.xml b/app/src/main/res/layout/activity_video.xml index ade3e150..9d4e693b 100644 --- a/app/src/main/res/layout/activity_video.xml +++ b/app/src/main/res/layout/activity_video.xml @@ -10,7 +10,7 @@ android:layout_height="match_parent" android:orientation="vertical"> - diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index 572e67c7..e9334e2b 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -319,5 +319,6 @@ Recommendations are as follows:\n Apply Deep Links After application, quickly jump to this app when opening relevant web pages MIUI has hidden this interface, so you need to open it from here and then add the link. + Switch Player Kernel \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index c383388c..92676fc0 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -325,5 +325,6 @@ 应用深层链接 应用后,打开相关网页后能快速跳转至本 App MIUI 隐藏了这个界面,所以你需要从这里打开然后添加链接 + 切换播放器内核 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cd99fc20..74c2a5e5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -329,5 +329,6 @@ 應用深層連結 應用後,打開相關網頁後能快速跳轉至本 App MIUI 隱藏了這個介面,所以你需要從這裡打開然後添加連結 + 切換播放器內核 diff --git a/app/src/main/res/xml/settings_home.xml b/app/src/main/res/xml/settings_home.xml index aeac8646..c5851f3f 100644 --- a/app/src/main/res/xml/settings_home.xml +++ b/app/src/main/res/xml/settings_home.xml @@ -4,7 +4,7 @@ - - - + + + - -