Twidere-App-Android-Twitter.../twidere/src/main/kotlin/org/mariotaku/twidere/fragment/VideoPageFragment.kt

316 lines
11 KiB
Kotlin

package org.mariotaku.twidere.fragment
import android.annotation.SuppressLint
import android.content.Context
import android.media.AudioManager
import android.media.MediaPlayer
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.util.Pair
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.MediaController
import android.widget.ProgressBar
import android.widget.TextView
import edu.tsinghua.hotmobi.HotMobiLogger
import edu.tsinghua.hotmobi.model.MediaDownloadEvent
import kotlinx.android.synthetic.main.layout_media_viewer_texture_video_view.*
import org.mariotaku.mediaviewer.library.CacheDownloadLoader
import org.mariotaku.mediaviewer.library.CacheDownloadMediaViewerFragment
import org.mariotaku.mediaviewer.library.subsampleimageview.SubsampleImageViewerFragment
import org.mariotaku.twidere.R
import org.mariotaku.twidere.TwidereConstants.EXTRA_ACCOUNT_KEY
import org.mariotaku.twidere.TwidereConstants.EXTRA_MEDIA
import org.mariotaku.twidere.activity.MediaViewerActivity
import org.mariotaku.twidere.model.ParcelableMedia
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.util.media.MediaExtra
import java.util.*
import java.util.concurrent.TimeUnit
class VideoPageFragment : CacheDownloadMediaViewerFragment(), MediaPlayer.OnPreparedListener,
MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener, View.OnClickListener {
private var mPlayAudio: Boolean = false
private var mVideoProgressRunnable: VideoPlayProgressRunnable? = null
private var mediaPlayer: MediaPlayer? = null
private var mMediaPlayerError: Int = 0
private var mediaDownloadEvent: MediaDownloadEvent? = null
override fun getDownloadExtra(): Any? {
val extra = MediaExtra()
extra.isUseThumbor = false
val fallbackUrlAndType = getBestVideoUrlAndType(media, FALLBACK_VIDEO_TYPES)
if (fallbackUrlAndType != null) {
extra.fallbackUrl = fallbackUrlAndType.first
}
return extra
}
val isLoopEnabled: Boolean
get() = arguments.getBoolean(EXTRA_LOOP, false)
override fun isAbleToLoad(): Boolean {
return downloadUri != null
}
override fun getDownloadUri(): Uri? {
val bestVideoUrlAndType = getBestVideoUrlAndType(media,
SUPPORTED_VIDEO_TYPES)
if (bestVideoUrlAndType != null && bestVideoUrlAndType.first != null) {
return Uri.parse(bestVideoUrlAndType.first)
}
return arguments.getParcelable<Uri>(SubsampleImageViewerFragment.EXTRA_MEDIA_URI)
}
override fun displayMedia(result: CacheDownloadLoader.Result) {
videoView.setVideoURI(result.cacheUri)
videoControl.visibility = View.GONE
setMediaViewVisible(true)
val activity = activity
activity?.supportInvalidateOptionsMenu()
}
override fun recycleMedia() {
}
override fun onCompletion(mp: MediaPlayer) {
updatePlayerState()
// mVideoViewProgress.removeCallbacks(mVideoProgressRunnable);
// mVideoViewProgress.setVisibility(View.GONE);
}
override fun onError(mp: MediaPlayer, what: Int, extra: Int): Boolean {
mediaPlayer = null
videoViewProgress.removeCallbacks(mVideoProgressRunnable)
videoViewProgress.visibility = View.GONE
videoControl.visibility = View.GONE
mMediaPlayerError = what
return true
}
override fun onPrepared(mp: MediaPlayer) {
if (userVisibleHint) {
mediaPlayer = mp
mMediaPlayerError = 0
mp.setScreenOnWhilePlaying(true)
updateVolume()
mp.isLooping = isLoopEnabled
mp.start()
videoViewProgress.visibility = View.VISIBLE
videoViewProgress.post(mVideoProgressRunnable)
updatePlayerState()
videoControl.visibility = View.VISIBLE
}
}
private fun updateVolume() {
volumeButton.setImageResource(if (mPlayAudio) R.drawable.ic_action_speaker_max else R.drawable.ic_action_speaker_muted)
val mp = mediaPlayer ?: return
try {
if (mPlayAudio) {
mp.setVolume(1f, 1f)
} else {
mp.setVolume(0f, 0f)
}
} catch (e: IllegalStateException) {
// Ignore
}
}
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
if (activity == null) return
if (isVisibleToUser) {
activity.supportInvalidateOptionsMenu()
} else if (videoView.isPlaying) {
videoView.pause()
updatePlayerState()
}
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setHasOptionsMenu(true)
var handler: Handler? = videoViewProgress.handler
if (handler == null) {
handler = Handler(activity.mainLooper)
}
val am = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
// Play audio by default if ringer mode on
mPlayAudio = am.ringerMode == AudioManager.RINGER_MODE_NORMAL
mVideoProgressRunnable = VideoPlayProgressRunnable(handler, videoViewProgress,
durationLabel, positionLabel, videoView)
videoViewOverlay.setOnClickListener(this)
videoView.setOnPreparedListener(this)
videoView.setOnErrorListener(this)
videoView.setOnCompletionListener(this)
playPauseButton.setOnClickListener(this)
volumeButton.setOnClickListener(this)
videoControl.visibility = View.GONE
startLoading(false)
setMediaViewVisible(false)
updateVolume()
}
@SuppressLint("SwitchIntDef")
private fun getBestVideoUrlAndType(media: ParcelableMedia?,
supportedTypes: Array<String>): Pair<String, String>? {
if (media == null) return null
when (media.type) {
ParcelableMedia.Type.VIDEO, ParcelableMedia.Type.ANIMATED_GIF -> {
if (media.video_info == null) {
return Pair.create<String, String>(media.media_url, null)
}
val firstMatch = media.video_info.variants.first { variant ->
supportedTypes.any { it.equals(variant.content_type, ignoreCase = true) }
} ?: return null
return Pair.create(firstMatch.url, firstMatch.content_type)
}
ParcelableMedia.Type.CARD_ANIMATED_GIF -> {
return Pair.create<String, String>(media.media_url, "video/mp4")
}
else -> {
return null
}
}
}
override fun onClick(v: View) {
when (v.id) {
R.id.volumeButton -> {
mPlayAudio = !mPlayAudio
updateVolume()
}
R.id.playPauseButton -> {
val mp = mediaPlayer
if (mp != null) {
if (mp.isPlaying) {
mp.pause()
} else {
mp.start()
}
}
updatePlayerState()
}
R.id.videoViewOverlay -> {
val activity = activity as MediaViewerActivity
if (videoControl.visibility == View.VISIBLE) {
videoControl.visibility = View.GONE
activity.setBarVisibility(false)
} else {
videoControl.visibility = View.VISIBLE
activity.setBarVisibility(true)
}
}
}
}
private fun updatePlayerState() {
val mp = mediaPlayer
if (mp != null) {
val playing = mp.isPlaying
playPauseButton.contentDescription = getString(if (playing) R.string.pause else R.string.play)
playPauseButton.setImageResource(if (playing) R.drawable.ic_action_pause else R.drawable.ic_action_play_arrow)
} else {
playPauseButton.contentDescription = getString(R.string.play)
playPauseButton.setImageResource(R.drawable.ic_action_play_arrow)
}
}
override fun onCreateMediaView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
return inflater.inflate(R.layout.layout_media_viewer_texture_video_view, container, false)
}
override fun onDownloadRequested(nonce: Long) {
super.onDownloadRequested(nonce)
val context = context
if (context != null) {
mediaDownloadEvent = MediaDownloadEvent.create(context, media, nonce)
} else {
mediaDownloadEvent = null
}
}
override fun onDownloadStart(total: Long, nonce: Long) {
super.onDownloadStart(total, nonce)
mediaDownloadEvent?.let {
if (it.nonce == nonce) {
it.setOpenedTime(System.currentTimeMillis())
it.setSize(total)
}
}
}
override fun onDownloadFinished(nonce: Long) {
super.onDownloadFinished(nonce)
if (mediaDownloadEvent != null && mediaDownloadEvent!!.nonce == nonce) {
mediaDownloadEvent!!.markEnd()
HotMobiLogger.getInstance(context).log(accountKey, mediaDownloadEvent!!)
mediaDownloadEvent = null
}
}
private val media: ParcelableMedia?
get() = arguments.getParcelable<ParcelableMedia>(EXTRA_MEDIA)
private val accountKey: UserKey
get() = arguments.getParcelable<UserKey>(EXTRA_ACCOUNT_KEY)
private class VideoPlayProgressRunnable internal constructor(
private val handler: Handler,
private val progressBar: ProgressBar,
private val durationLabel: TextView,
private val positionLabel: TextView,
private val mediaPlayerControl: MediaController.MediaPlayerControl
) : Runnable {
init {
progressBar.max = 1000
}
override fun run() {
val duration = mediaPlayerControl.duration
val position = mediaPlayerControl.currentPosition
if (duration <= 0 || position < 0) return
progressBar.progress = Math.round(1000 * position / duration.toFloat())
val durationSecs = TimeUnit.SECONDS.convert(duration.toLong(), TimeUnit.MILLISECONDS)
val positionSecs = TimeUnit.SECONDS.convert(position.toLong(), TimeUnit.MILLISECONDS)
durationLabel.text = String.format(Locale.ROOT, "%02d:%02d", durationSecs / 60, durationSecs % 60)
positionLabel.text = String.format(Locale.ROOT, "%02d:%02d", positionSecs / 60, positionSecs % 60)
handler.postDelayed(this, 16)
}
}
companion object {
const val EXTRA_LOOP = "loop"
private val SUPPORTED_VIDEO_TYPES: Array<String>
private val FALLBACK_VIDEO_TYPES: Array<String>
init {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
SUPPORTED_VIDEO_TYPES = arrayOf("video/mp4")
} else {
SUPPORTED_VIDEO_TYPES = arrayOf("video/webm", "video/mp4")
}
FALLBACK_VIDEO_TYPES = arrayOf("video/mp4")
}
}
}