improved media player

This commit is contained in:
Mariotaku Lee 2017-02-02 22:08:04 +08:00
parent 22a38fa64d
commit ed7ebbcb1c
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
10 changed files with 141 additions and 29 deletions

View File

@ -176,7 +176,7 @@ dependencies {
compile "com.github.mariotaku.CommonsLibrary:text:$mariotaku_commons_library_version"
compile "com.github.mariotaku.CommonsLibrary:text-kotlin:$mariotaku_commons_library_version"
compile 'com.github.mariotaku:KPreferences:0.9.5'
compile 'com.github.mariotaku:Chameleon:0.9.12'
compile 'com.github.mariotaku:Chameleon:0.9.13-SNAPSHOT'
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile 'nl.komponents.kovenant:kovenant:3.3.0'
compile 'nl.komponents.kovenant:kovenant-android:3.3.0'

View File

@ -18,14 +18,26 @@ public class ActivitySupport {
private ActivitySupport() {
}
public static void setImmersive(Activity activity, boolean immersive) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) return;
ActivitySupportJBMR2.setImmersive(activity, immersive);
}
public static void setTaskDescription(Activity activity, TaskDescriptionCompat taskDescription) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return;
ActivityAccessorL.setTaskDescription(activity, taskDescription);
ActivitySupportL.setTaskDescription(activity, taskDescription);
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
private static class ActivitySupportJBMR2 {
static void setImmersive(Activity activity, boolean immersive) {
activity.setImmersive(immersive);
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
static class ActivityAccessorL {
private ActivityAccessorL() {
private static class ActivitySupportL {
private ActivitySupportL() {
}
public static void setTaskDescription(Activity activity, TaskDescriptionCompat taskDescription) {

View File

@ -16,6 +16,11 @@ public class WindowSupport {
WindowAccessorLollipop.setStatusBarColor(window, color);
}
public static void setNavigationBarColor(Window window, int color) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return;
WindowAccessorLollipop.setNavigationBarColor(window, color);
}
public static void setSharedElementsUseOverlay(Window window, boolean sharedElementsUseOverlay) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return;
WindowAccessorLollipop.setSharedElementsUseOverlay(window, sharedElementsUseOverlay);
@ -27,6 +32,10 @@ public class WindowSupport {
window.setStatusBarColor(color);
}
public static void setNavigationBarColor(Window window, int color) {
window.setNavigationBarColor(color);
}
public static void setSharedElementsUseOverlay(Window window, boolean sharedElementsUseOverlay) {
window.setSharedElementsUseOverlay(sharedElementsUseOverlay);
}

View File

@ -20,6 +20,7 @@ import android.annotation.SuppressLint
import android.content.ActivityNotFoundException
import android.content.Intent
import android.graphics.Color
import android.graphics.Rect
import android.net.Uri
import android.os.Build
import android.os.Bundle
@ -35,6 +36,7 @@ import android.support.v7.app.WindowDecorActionBar
import android.support.v7.app.containerView
import android.view.Menu
import android.view.MenuItem
import android.view.WindowManager
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_media_viewer.*
import org.mariotaku.chameleon.Chameleon
@ -47,6 +49,7 @@ import org.mariotaku.twidere.TwidereConstants.*
import org.mariotaku.twidere.activity.iface.IControlBarActivity.ControlBarShowHideHelper
import org.mariotaku.twidere.activity.iface.IExtendedActivity
import org.mariotaku.twidere.fragment.*
import org.mariotaku.twidere.fragment.iface.IBaseFragment
import org.mariotaku.twidere.model.ParcelableMedia
import org.mariotaku.twidere.model.ParcelableStatus
import org.mariotaku.twidere.provider.CacheProvider
@ -86,18 +89,24 @@ class MediaViewerActivity : BaseActivity(), IMediaViewerActivity, MediaSwipeClos
}
override fun onCreate(savedInstanceState: Bundle?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)
}
super.onCreate(savedInstanceState)
GeneralComponentHelper.build(this).inject(this)
mediaViewerHelper = IMediaViewerActivity.Helper(this)
controlBarShowHideHelper = ControlBarShowHideHelper(this)
mediaViewerHelper.onCreate(savedInstanceState)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.elevation = 0f
swipeContainer.listener = this
swipeContainer.backgroundAlpha = 1f
WindowSupport.setStatusBarColor(window, Color.TRANSPARENT)
activityLayout.setStatusBarColor(overrideTheme.colorToolbar)
activityLayout.setWindowInsetsListener { l, t, r, b ->
activityLayout.setStatusBarHeight(t - ThemeUtils.getActionBarHeight(this))
val statusBarHeight = t - ThemeUtils.getActionBarHeight(this)
activityLayout.setStatusBarHeight(statusBarHeight)
onFitSystemWindows(Rect(l, t, r, b))
}
}
@ -238,6 +247,7 @@ class MediaViewerActivity : BaseActivity(), IMediaViewerActivity, MediaSwipeClos
}
override fun setBarVisibility(visible: Boolean) {
if (isBarShowing == visible) return
setControlBarVisibleAnimate(visible)
}
@ -269,6 +279,8 @@ class MediaViewerActivity : BaseActivity(), IMediaViewerActivity, MediaSwipeClos
}
ParcelableMedia.Type.ANIMATED_GIF, ParcelableMedia.Type.CARD_ANIMATED_GIF -> {
args.putBoolean(VideoPageFragment.EXTRA_LOOP, true)
args.putBoolean(VideoPageFragment.EXTRA_DISABLE_CONTROL, true)
args.putBoolean(VideoPageFragment.EXTRA_DEFAULT_MUTE, true)
return Fragment.instantiate(this, VideoPageFragment::class.java.name, args) as MediaViewerFragment
}
ParcelableMedia.Type.VIDEO -> {
@ -351,7 +363,6 @@ class MediaViewerActivity : BaseActivity(), IMediaViewerActivity, MediaSwipeClos
// Some device will throw this exception
hideOffsetNotSupported = true
}
}
notifyControlBarOffsetChanged()
}
@ -360,6 +371,16 @@ class MediaViewerActivity : BaseActivity(), IMediaViewerActivity, MediaSwipeClos
controlBarShowHideHelper.setControlBarVisibleAnimate(visible, listener)
}
override fun onFitSystemWindows(insets: Rect) {
super.onFitSystemWindows(insets)
val adapter = viewPager.adapter
val fragment = adapter.instantiateItem(viewPager, viewPager.currentItem)
if (fragment is IBaseFragment<*>) {
fragment.requestFitSystemWindows()
}
}
private fun processShareIntent(intent: Intent) {
val status = status ?: return
intent.putExtra(Intent.EXTRA_SUBJECT, IntentUtils.getStatusShareSubject(this, status))

View File

@ -395,10 +395,13 @@ abstract class AbsActivitiesFragment protected constructor() :
override val reachingEnd: Boolean
get() {
val lm = layoutManager
val lastPosition = lm.findLastCompletelyVisibleItemPosition()
var lastPosition = lm.findLastCompletelyVisibleItemPosition()
if (lastPosition == RecyclerView.NO_POSITION) {
lastPosition = lm.findLastVisibleItemPosition()
}
val itemCount = adapter.itemCount
var finalPos = itemCount - 1
for (i in lastPosition + 1..itemCount - 1) {
for (i in lastPosition + 1 until itemCount) {
if (adapter.getItemViewType(i) != ParcelableActivitiesAdapter.ITEM_VIEW_TYPE_EMPTY) {
finalPos = i - 1
break
@ -407,9 +410,6 @@ abstract class AbsActivitiesFragment protected constructor() :
return finalPos >= itemCount - 1
}
override val reachingStart: Boolean
get() = super.reachingStart
protected open fun createMessageBusCallback(): Any {
return StatusesBusCallback()
}

View File

@ -62,13 +62,23 @@ abstract class AbsContentListRecyclerViewFragment<A : LoadMoreSupportAdapter<Rec
override val reachingEnd: Boolean
get() {
val lm = layoutManager
return lm.findLastCompletelyVisibleItemPosition() >= lm.itemCount - 1
var itemPos = lm.findLastCompletelyVisibleItemPosition()
if (itemPos == RecyclerView.NO_POSITION) {
// No completely visible item, find visible item instead
itemPos = lm.findLastVisibleItemPosition()
}
return itemPos >= lm.itemCount - 1
}
override val reachingStart: Boolean
get() {
val lm = layoutManager
return lm.findFirstCompletelyVisibleItemPosition() <= 0
var itemPos = lm.findFirstCompletelyVisibleItemPosition()
if (itemPos == RecyclerView.NO_POSITION) {
// No completely visible item, find visible item instead
itemPos = lm.findFirstVisibleItemPosition()
}
return itemPos == 0
}
}

View File

@ -520,10 +520,26 @@ class StatusFragment : BaseFragment(), LoaderCallbacks<SingleResponse<Parcelable
}
override val reachingEnd: Boolean
get() = layoutManager.findLastCompletelyVisibleItemPosition() >= adapter.itemCount - 1
get() {
val lm = layoutManager
var itemPos = lm.findLastCompletelyVisibleItemPosition()
if (itemPos == RecyclerView.NO_POSITION) {
// No completely visible item, find visible item instead
itemPos = lm.findLastVisibleItemPosition()
}
return itemPos >= lm.itemCount - 1
}
override val reachingStart: Boolean
get() = layoutManager.findFirstCompletelyVisibleItemPosition() <= 1
get() {
val lm = layoutManager
var itemPos = lm.findFirstCompletelyVisibleItemPosition()
if (itemPos == RecyclerView.NO_POSITION) {
// No completely visible item, find visible item instead
itemPos = lm.findFirstVisibleItemPosition()
}
return itemPos <= 1
}
private val status: ParcelableStatus?
get() = adapter.status

View File

@ -8,19 +8,15 @@ import android.support.v4.content.Loader
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.StaggeredGridLayoutManager
import android.text.TextUtils
import android.view.View
import org.apache.commons.lang3.ArrayUtils
import org.mariotaku.twidere.adapter.StaggeredGridParcelableStatusesAdapter
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter
import org.mariotaku.twidere.constant.IntentConstants.*
import org.mariotaku.twidere.loader.MediaTimelineLoader
import org.mariotaku.twidere.loader.iface.IExtendedLoader
import org.mariotaku.twidere.model.ParcelableMedia
import org.mariotaku.twidere.model.ParcelableStatus
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.util.IntentUtils
import org.mariotaku.twidere.view.HeaderDrawerLayout.DrawerCallback
import org.mariotaku.twidere.view.holder.GapViewHolder
import org.mariotaku.twidere.view.holder.iface.IStatusViewHolder
/**
@ -42,13 +38,21 @@ class UserMediaTimelineFragment : AbsContentRecyclerViewFragment<StaggeredGridPa
override val reachingEnd: Boolean
get() {
val lm = layoutManager
return ArrayUtils.contains(lm.findLastCompletelyVisibleItemPositions(null), lm.itemCount - 1)
var visiblePos = lm.findLastCompletelyVisibleItemPositions(null)
if (visiblePos.all { it == RecyclerView.NO_POSITION }) {
visiblePos = lm.findLastVisibleItemPositions(null)
}
return visiblePos.contains(lm.itemCount - 1)
}
override val reachingStart: Boolean
get() {
val lm = layoutManager
return ArrayUtils.contains(lm.findFirstCompletelyVisibleItemPositions(null), 0)
var visiblePos = lm.findFirstCompletelyVisibleItemPositions(null)
if (visiblePos.all { it == RecyclerView.NO_POSITION }) {
visiblePos = lm.findFirstVisibleItemPositions(null)
}
return visiblePos.contains(0)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {

View File

@ -2,6 +2,9 @@ package org.mariotaku.twidere.fragment
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Color
import android.graphics.Rect
import android.graphics.drawable.ColorDrawable
import android.media.AudioManager
import android.media.MediaPlayer
import android.net.Uri
@ -28,16 +31,21 @@ 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.activity.iface.IControlBarActivity
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_POSITION
import org.mariotaku.twidere.fragment.iface.IBaseFragment
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, IControlBarActivity.ControlBarOffsetListener {
class VideoPageFragment : CacheDownloadMediaViewerFragment(), IBaseFragment<VideoPageFragment>,
MediaPlayer.OnPreparedListener, MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener,
View.OnClickListener, IControlBarActivity.ControlBarOffsetListener {
private val isLoopEnabled: Boolean get() = arguments.getBoolean(EXTRA_LOOP, false)
private val isControlDisabled: Boolean get() = arguments.getBoolean(EXTRA_DISABLE_CONTROL, false)
private val isMutedByDefault: Boolean get() = arguments.getBoolean(EXTRA_DEFAULT_MUTE, false)
private val media: ParcelableMedia? get() = arguments.getParcelable<ParcelableMedia>(EXTRA_MEDIA)
private val accountKey: UserKey get() = arguments.getParcelable<UserKey>(EXTRA_ACCOUNT_KEY)
@ -76,11 +84,14 @@ class VideoPageFragment : CacheDownloadMediaViewerFragment(), MediaPlayer.OnPrep
val am = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
// Play audio by default if ringer mode on
playAudio = am.ringerMode == AudioManager.RINGER_MODE_NORMAL
playAudio = !isMutedByDefault && am.ringerMode == AudioManager.RINGER_MODE_NORMAL
videoProgressRunnable = VideoPlayProgressRunnable(handler, videoViewProgress,
durationLabel, positionLabel, videoView)
if (savedInstanceState != null) {
positionBackup = savedInstanceState.getInt(EXTRA_POSITION)
}
videoViewOverlay.setOnClickListener(this)
videoView.setOnPreparedListener(this)
@ -91,6 +102,13 @@ class VideoPageFragment : CacheDownloadMediaViewerFragment(), MediaPlayer.OnPrep
volumeButton.setOnClickListener(this)
videoControl.visibility = View.GONE
videoContainer.setAspectRatioSource(aspectRatioSource)
if (isLoopEnabled) {
videoViewProgress.thumb = ColorDrawable(Color.TRANSPARENT)
videoViewProgress.isEnabled = false
} else {
videoViewProgress.isEnabled = true
}
videoViewProgress.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
private var paused: Boolean = false
@ -140,6 +158,17 @@ class VideoPageFragment : CacheDownloadMediaViewerFragment(), MediaPlayer.OnPrep
super.onDetach()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt(EXTRA_POSITION, positionBackup)
}
override fun onViewStateRestored(savedInstanceState: Bundle?) {
super.onViewStateRestored(savedInstanceState)
requestFitSystemWindows()
}
override fun getDownloadExtra(): Any? {
val extra = MediaExtra()
extra.isUseThumbor = false
@ -154,7 +183,6 @@ class VideoPageFragment : CacheDownloadMediaViewerFragment(), MediaPlayer.OnPrep
return downloadUri != null
}
override fun getDownloadUri(): Uri? {
val bestVideoUrlAndType = getBestVideoUrlAndType(media, SUPPORTED_VIDEO_TYPES)
if (bestVideoUrlAndType != null && bestVideoUrlAndType.first != null) {
@ -180,8 +208,10 @@ class VideoPageFragment : CacheDownloadMediaViewerFragment(), MediaPlayer.OnPrep
override fun onControlBarOffsetChanged(activity: IControlBarActivity, offset: Float) {
videoControl.translationY = (1 - offset) * videoControl.height
videoControl.alpha = offset
}
override fun onError(mp: MediaPlayer, what: Int, extra: Int): Boolean {
mediaPlayer = null
videoViewProgress.removeCallbacks(videoProgressRunnable)
@ -205,7 +235,7 @@ class VideoPageFragment : CacheDownloadMediaViewerFragment(), MediaPlayer.OnPrep
videoViewProgress.visibility = View.VISIBLE
videoViewProgress.post(videoProgressRunnable)
updatePlayerState()
videoControl.visibility = View.VISIBLE
videoControl.visibility = if (isControlDisabled) View.GONE else View.VISIBLE
}
}
@ -292,6 +322,16 @@ class VideoPageFragment : CacheDownloadMediaViewerFragment(), MediaPlayer.OnPrep
}
}
override fun fitSystemWindows(insets: Rect) {
val lp = videoControl.layoutParams
if (lp is ViewGroup.MarginLayoutParams) {
lp.bottomMargin = insets.bottom
}
}
override fun executeAfterFragmentResumed(useHandler: Boolean, action: (VideoPageFragment) -> Unit) {
// No-op
}
@SuppressLint("SwitchIntDef")
private fun getBestVideoUrlAndType(media: ParcelableMedia?, supportedTypes: Array<String>): Pair<String, String>? {
@ -377,6 +417,8 @@ class VideoPageFragment : CacheDownloadMediaViewerFragment(), MediaPlayer.OnPrep
companion object {
const val EXTRA_LOOP = "loop"
const val EXTRA_DISABLE_CONTROL = "disable_control"
const val EXTRA_DEFAULT_MUTE = "default_mute"
private val SUPPORTED_VIDEO_TYPES: Array<String>
private val FALLBACK_VIDEO_TYPES: Array<String> = arrayOf("video/mp4")

View File

@ -47,10 +47,10 @@
<RelativeLayout
android:id="@+id/videoControl"
style="?actionBarSplitStyle"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
android:layout_gravity="bottom"
android:clickable="true"
android:gravity="center_vertical"
android:orientation="horizontal"
tools:ignore="UselessParent">
@ -102,7 +102,6 @@
android:textSize="@dimen/text_size_extra_small"
tools:text="--:--"/>
<ImageButton
android:id="@+id/volumeButton"
android:layout_width="wrap_content"
@ -115,7 +114,6 @@
android:minWidth="@dimen/element_size_normal"
android:src="@drawable/ic_action_speaker_max"/>
</RelativeLayout>
</FrameLayout>
</RelativeLayout>