Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Exoplayer - Simple ExoPlayerHelper
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.google.android.exoplayer2.ui.PlayerView
android:id="@+id/playerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:controller_layout_id="@layout/custom_exo_controller_layout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.master.exoplayersample.VideoTimelineView
android:id="@+id/range_slider"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tvMessage"
app:layout_constraintVertical_bias="0.102" />
<TextView
android:id="@+id/tvMessage"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_marginBottom="8dp"
android:text="TextView"
android:gravity="center"
android:textColor="@color/colorAccent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
tools:text="This is textview" />
</android.support.constraint.ConstraintLayout>
package com.master.exoplayersample;
import android.content.Context;
public class AndroidUtilities {
public static float density = 1;
public static void init(Context context) {
density = context.getResources().getDisplayMetrics().density;
}
public static int dp(float value) {
if (value == 0) {
return 0;
}
return (int) Math.ceil(density * value);
}
public static int dp2(float value) {
if (value == 0) {
return 0;
}
return (int) Math.floor(density * value);
}
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.master.exoplayersample"
minSdkVersion 15
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}
dataBinding{
enabled true
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'com.google.android.exoplayer:exoplayer:2.9.2'
}
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageButton
android:id="@id/exo_play"
style="@style/ExoMediaButton.Play"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="center"
android:background="#CC000000" />
<ImageButton
android:id="@id/exo_pause"
style="@style/ExoMediaButton.Pause"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="center"
android:background="#CC000000" />
</FrameLayout>
package com.master.exoplayersample
import android.arch.lifecycle.Lifecycle
import android.arch.lifecycle.LifecycleObserver
import android.arch.lifecycle.OnLifecycleEvent
import android.net.Uri
import android.os.Handler
import android.support.v7.app.AppCompatActivity
import android.util.Log
import android.view.View
import com.fanclips.R
import com.google.android.exoplayer2.*
import com.google.android.exoplayer2.source.ClippingMediaSource
import com.google.android.exoplayer2.source.ConcatenatingMediaSource
import com.google.android.exoplayer2.source.ExtractorMediaSource
import com.google.android.exoplayer2.source.MediaSource
import com.google.android.exoplayer2.source.dash.DashMediaSource
import com.google.android.exoplayer2.source.hls.HlsMediaSource
import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
import com.google.android.exoplayer2.ui.PlayerView
import com.google.android.exoplayer2.upstream.*
import com.google.android.exoplayer2.upstream.cache.*
import com.google.android.exoplayer2.util.Util
import java.io.File
class ExoPlayerHelper(val mContext: AppCompatActivity, private val playerView: PlayerView, enableCache: Boolean = true) : LifecycleObserver {
private var mDataSourceFactory: DataSource.Factory
private var mPlayer: SimpleExoPlayer
var cacheSizeInMb: Long = 500
private var simpleCache: SimpleCache? = null
init {
//For lifecycle
mContext.lifecycle.addObserver(this)
val bandwidthMeter = DefaultBandwidthMeter()
mDataSourceFactory = DefaultDataSourceFactory(mContext, Util.getUserAgent(mContext, mContext.getString(R.string.application_name)), bandwidthMeter)
// LoadControl that controls when the MediaSource buffers more media, and how much media is buffered.
// LoadControl is injected when the player is created.
val builder = DefaultLoadControl.Builder();
builder.setAllocator(DefaultAllocator(true, 2 * 1024 * 1024));
builder.setBufferDurationsMs(5000, 5000, 5000, 5000);
builder.setPrioritizeTimeOverSizeThresholds(true);
val mLoadControl = builder.createDefaultLoadControl();
if (enableCache) {
val evictor = LeastRecentlyUsedCacheEvictor(cacheSizeInMb * 1024 * 1024)
val file = File(mContext.getCacheDir(), "media")
if (simpleCache == null) {
simpleCache = SimpleCache(file, evictor)
}
mDataSourceFactory = CacheDataSourceFactory(
simpleCache,
mDataSourceFactory,
FileDataSourceFactory(),
CacheDataSinkFactory(simpleCache, (2 * 1024 * 1024).toLong()),
CacheDataSource.FLAG_BLOCK_ON_CACHE or CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR,
object : CacheDataSource.EventListener {
override fun onCacheIgnored(reason: Int) {
Log.d("ZAQ", "onCacheIgnored")
}
override fun onCachedBytesRead(cacheSizeBytes: Long, cachedBytesRead: Long) {
Log.d("ZAQ", "onCachedBytesRead , cacheSizeBytes: $cacheSizeBytes cachedBytesRead: $cachedBytesRead")
}
})
}
mPlayer = ExoPlayerFactory.newSimpleInstance(
mContext,
DefaultRenderersFactory(mContext),
DefaultTrackSelector(),
mLoadControl);
playerView.player = mPlayer
}
private var mediaSource: MediaSource? = null
private var isPreparing = false //This flag is used only for callback
/**
* Sets the url to play
*
* @param url url to play
* @param autoPlay whether url will play as soon it Loaded/Prepared
*/
fun setUrl(url: String, autoPlay: Boolean = false) {
mediaSource = buildMediaSource(Uri.parse(url))
mPlayer.playWhenReady = autoPlay
isPreparing = true
mPlayer.prepare(mediaSource)
}
/**
* Sets the url to play
*
* @param urls url to play
* @param autoPlay whether url will play as soon it Loaded/Prepared
*/
fun setUrls(urls: ArrayList<String>, autoPlay: Boolean = false) {
val concatenationMediaSource = ConcatenatingMediaSource();
urls.forEach {
concatenationMediaSource.addMediaSource(buildMediaSource(Uri.parse(it)))
}
mPlayer.playWhenReady = autoPlay
isPreparing = true
mPlayer.prepare(mediaSource)
}
/**
* Trim or clip media to given start and end milliseconds,
* Ensure you must call this method after [setUrl] method call
* You Make sure start time < end time ( Something you do :) )
*
* @param start starting time in millisecond
* @param end ending time in millisecond
*/
fun clip(start: Long, end: Long) {
if (mediaSource != null) {
mediaSource = ClippingMediaSource(mediaSource, start * 1000, end * 1000)
}
mPlayer.prepare(mediaSource)
}
private fun buildMediaSource(uri: Uri): MediaSource {
val type = Util.inferContentType(uri)
when (type) {
C.TYPE_SS -> return SsMediaSource.Factory(mDataSourceFactory).createMediaSource(uri)
C.TYPE_DASH -> return DashMediaSource.Factory(mDataSourceFactory).createMediaSource(uri)
C.TYPE_HLS -> return HlsMediaSource.Factory(mDataSourceFactory).createMediaSource(uri)
C.TYPE_OTHER -> return ExtractorMediaSource.Factory(mDataSourceFactory).createMediaSource(uri)
else -> {
throw IllegalStateException("Unsupported type: $type")
}
}
}
/**
* Used to start player
* Ensure you must call this method after [setUrl] method call
*/
fun play() {
mPlayer.playWhenReady = true
}
/**
* Used to pause player
* Ensure you must call this method after [setUrl] method call
*/
fun pause() {
mPlayer.playWhenReady = false
}
/**
* Used to stop player
* Ensure you must call this method after [setUrl] method call
*/
fun stop() {
mPlayer.stop()
}
/**
* Used to seek player to given position(in milliseconds)
* Ensure you must call this method after [setUrl] method call
*/
fun seekTo(positionMs: Long) {
mPlayer.seekTo(positionMs)
}
val durationHandler = Handler()
private var durationRunnable: Runnable? = null
fun startTimer() {
if (durationRunnable != null)
durationHandler.postDelayed(durationRunnable, 500)
}
fun stopTimer() {
if (durationRunnable != null)
durationHandler.removeCallbacks(durationRunnable)
}
/**
* Returns SimpleExoPlayer instance you can use it for your own implementation
*/
fun getPlayer(): SimpleExoPlayer {
return mPlayer
}
/**
* Used to set different quality url of existing video/audio
*/
fun setQualityUrl(qualityUrl: String) {
val currentPosition = mPlayer.currentPosition
mediaSource = buildMediaSource(Uri.parse(qualityUrl))
mPlayer.prepare(mediaSource)
mPlayer.seekTo(currentPosition)
}
/**
* Normal speed is 1f and double the speed would be 2f.
*/
fun setSpeed(speed: Float) {
val param = PlaybackParameters(speed);
mPlayer.setPlaybackParameters(param)
}
//Life Cycle
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
private fun onPause() {
// simpleCache?.release()
mPlayer.playWhenReady = false
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
private fun onDestroy() {
simpleCache?.release()
mPlayer.playWhenReady = false
}
//LISTENERS
/**
* Listener that used for most popular callbacks
*/
var listener: Listener? = null
set(value) {
mPlayer.addListener(object : Player.EventListener {
override fun onPlayerError(error: ExoPlaybackException?) {
value?.onError(error)
}
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
if (isPreparing && playbackState == Player.STATE_READY) {
isPreparing = false
value?.onPlayerReady()
}
if (playbackState == Player.STATE_BUFFERING) {
value?.onBuffering(true)
} else {
value?.onBuffering(false)
}
if (playWhenReady) {
startTimer()
value?.onStart()
} else {
stopTimer()
value?.onStop()
}
}
})
playerView.setControllerVisibilityListener { visibility ->
value?.onToggleControllerVisible(visibility == View.VISIBLE)
}
durationRunnable = Runnable {
value?.onProgress(mPlayer.currentPosition)
if (mPlayer.playWhenReady) {
durationHandler.postDelayed(durationRunnable, 500)
}
}
}
interface Listener {
fun onPlayerReady() {}
fun onStart() {}
fun onStop() {}
fun onProgress(positionMs: Long) {}
fun onError(error: ExoPlaybackException?) {}
fun onBuffering(isBuffering: Boolean) {}
fun onToggleControllerVisible(isVisible: Boolean) {}
}
}
package com.master.exoplayersample
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.util.Log
import com.google.android.exoplayer2.ExoPlaybackException
import kotlinx.android.synthetic.main.activity_main.*
import kotlin.math.roundToLong
class MainActivity : AppCompatActivity() {
var leftProgress: Float = 0f
var rightProgress: Float = 0f
lateinit var exoPlayerHelper: ExoPlayerHelper
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
AndroidUtilities.init(this)
// val file="https://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_320x180.mp4"
val file = "/storage/emulated/0/WhatsApp/Media/WhatsApp Video/VID-20181209-WA0001.mp4"
exoPlayerHelper = ExoPlayerHelper(this, playerView, enableCache = false)
exoPlayerHelper.setUrl(file, autoPlay = false)
// exoPlayerHelper.clip(21000L, 41000L)
// exoPlayerHelper.seekTo(21000L)
// exoPlayerHelper.setSpeed(2.5f)
// exoPlayerHelper.play()
// exoPlayerHelper.pause()
// exoPlayerHelper.stop()
// exoPlayerHelper.getCurrentPosition()
// exoPlayerHelper.getPlayer()
// exoPlayerHelper.setQualityUrl("")
val TAG = "TAG"
exoPlayerHelper.listener = object : ExoPlayerHelper.Listener {
override fun onProgress(positionMs: Long) {
super.onProgress(positionMs)
Log.d(TAG, "onProgress $positionMs")
if (positionMs >= rightProgress) {
exoPlayerHelper.pause()
}
}
override fun onPlayerReady() {
Log.d(TAG, "onPlayerReady")
}
override fun onBuffering(isBuffering: Boolean) {
Log.d(TAG, "onBuffering: ${isBuffering}")
}
override fun onError(error: ExoPlaybackException?) {
Log.d(TAG, "onError: ${error}")
}
override fun onStart() {
super.onStart()
Log.d(TAG, "onStart")
}
override fun onStop() {
super.onStop()
Log.d(TAG, "onStop")
exoPlayerHelper.seekTo(leftProgress.roundToLong())
}
override fun onToggleControllerVisible(isVisible: Boolean) {
Log.d(TAG, "onToggleControllerVisible:${isVisible}")
}
}
//------Trimmer
range_slider.setVideoPath(file)
range_slider.setMaxProgressDiffInSec(200f)
range_slider.setMinProgressDiffInSec(2f)
leftProgress = range_slider.leftProgressInSec
rightProgress = range_slider.rightProgressInSec
tvMessage.setText("$leftProgress-$rightProgress")
exoPlayerHelper.seekTo(leftProgress.roundToLong())
// range_slider.setMaxProgressDiff(0.5f)
// range_slider.setMinProgressDiff(0.2f)
// range_slider.setRoundFrames(true)
range_slider.setDelegate(object : VideoTimelineView.VideoTimelineViewDelegate {
override fun onLeftProgressChanged(progress: Float) {
leftProgress = range_slider.leftProgressInSec
rightProgress = range_slider.rightProgressInSec
tvMessage.setText("$leftProgress-$rightProgress")
exoPlayerHelper.seekTo(leftProgress.roundToLong())
}
override fun onRightProgressChanged(progress: Float) {
leftProgress = range_slider.leftProgressInSec
rightProgress = range_slider.rightProgressInSec
tvMessage.setText("$leftProgress-$rightProgress")
exoPlayerHelper.seekTo(leftProgress.roundToLong())
}
override fun didStartDragging() {
}
override fun didStopDragging() {
}
})
}
}
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.2.61'
repositories {
google()
jcenter()
maven { url "https://jitpack.io" }
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
maven { url "https://jitpack.io" }
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
package com.master.exoplayersample;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.media.MediaMetadataRetriever;
import android.os.AsyncTask;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import java.util.ArrayList;
public class VideoTimelineView extends View {
private long videoLength = 0;
private float progressLeft;
private float progressRight = 1;
private Paint paint;
private Paint paint2;
private boolean pressedLeft;
private boolean pressedRight;
private float pressDx;
private MediaMetadataRetriever mediaMetadataRetriever;
private VideoTimelineViewDelegate delegate;
private ArrayList<Bitmap> frames = new ArrayList<>();
private AsyncTask<Integer, Integer, Bitmap> currentTask;
private static final Object sync = new Object();
private long frameTimeOffset;
private int frameWidth;
private int frameHeight;
private int framesToLoad;
private float maxProgressDiff = 1.0f;
private float minProgressDiff = 0.0f;
private boolean isRoundFrames;
private Rect rect1;
private Rect rect2;
public interface VideoTimelineViewDelegate {
void onLeftProgressChanged(float progress);
void onRightProgressChanged(float progress);
void didStartDragging();
void didStopDragging();
}
public VideoTimelineView(Context context) {
super(context);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(0xffffffff);
paint2 = new Paint();
paint2.setColor(0x7f000000);
}
public VideoTimelineView(Context context, AttributeSet attrs) {
super(context, attrs);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(0xffffffff);
paint2 = new Paint();
paint2.setColor(0x7f000000);
}
public VideoTimelineView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(0xffffffff);
paint2 = new Paint();
paint2.setColor(0x7f000000);
}
//seconds
public float getLeftProgressInSec() {
return videoLength * progressLeft;
}
public float getRightProgressInSec() {
return videoLength * progressRight;
}
public void setMinProgressDiffInSec(float valueInSec) {
setMinProgressDiff((valueInSec*1000) / videoLength);
}
public void setMaxProgressDiffInSec(float valueInSec) {
setMaxProgressDiff((valueInSec*1000) / videoLength);
}
//seconds end
public float getLeftProgress() {
return progressLeft;
}
public float getRightProgress() {
return progressRight;
}
public void setMinProgressDiff(float value) {
minProgressDiff = value;
}
public void setMaxProgressDiff(float value) {
maxProgressDiff = value;
if (progressRight - progressLeft > maxProgressDiff) {
progressRight = progressLeft + maxProgressDiff;
invalidate();
}
}
public void setRoundFrames(boolean value) {
isRoundFrames = value;
if (isRoundFrames) {
rect1 = new Rect(AndroidUtilities.dp(14), AndroidUtilities.dp(14), AndroidUtilities.dp(14 + 28), AndroidUtilities.dp(14 + 28));
rect2 = new Rect();
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event == null) {
return false;
}
float x = event.getX();
float y = event.getY();
int width = getMeasuredWidth() - AndroidUtilities.dp(32);
int startX = (int) (width * progressLeft) + AndroidUtilities.dp(16);
int endX = (int) (width * progressRight) + AndroidUtilities.dp(16);
if (event.getAction() == MotionEvent.ACTION_DOWN) {
getParent().requestDisallowInterceptTouchEvent(true);
if (mediaMetadataRetriever == null) {
return false;
}
int additionWidth = AndroidUtilities.dp(15);
if (startX - additionWidth <= x && x <= startX + additionWidth && y >= 0 && y <= getMeasuredHeight()) {
if (delegate != null) {
delegate.didStartDragging();
}
pressedLeft = true;
pressDx = (int) (x - startX);
invalidate();
return true;
} else if (endX - additionWidth <= x && x <= endX + additionWidth && y >= 0 && y <= getMeasuredHeight()) {
if (delegate != null) {
delegate.didStartDragging();
}
pressedRight = true;
pressDx = (int) (x - endX);
invalidate();
return true;
}
} else if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) {
if (pressedLeft) {
if (delegate != null) {
delegate.didStopDragging();
}
pressedLeft = false;
return true;
} else if (pressedRight) {
if (delegate != null) {
delegate.didStopDragging();
}
pressedRight = false;
return true;
}
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
if (pressedLeft) {
startX = (int) (x - pressDx);
if (startX < AndroidUtilities.dp(16)) {
startX = AndroidUtilities.dp(16);
} else if (startX > endX) {
startX = endX;
}
progressLeft = (float) (startX - AndroidUtilities.dp(16)) / (float) width;
if (progressRight - progressLeft > maxProgressDiff) {
progressRight = progressLeft + maxProgressDiff;
} else if (minProgressDiff != 0 && progressRight - progressLeft < minProgressDiff) {
progressLeft = progressRight - minProgressDiff;
if (progressLeft < 0) {
progressLeft = 0;
}
}
if (delegate != null) {
delegate.onLeftProgressChanged(progressLeft);
}
invalidate();
return true;
} else if (pressedRight) {
endX = (int) (x - pressDx);
if (endX < startX) {
endX = startX;
} else if (endX > width + AndroidUtilities.dp(16)) {
endX = width + AndroidUtilities.dp(16);
}
progressRight = (float) (endX - AndroidUtilities.dp(16)) / (float) width;
if (progressRight - progressLeft > maxProgressDiff) {
progressLeft = progressRight - maxProgressDiff;
} else if (minProgressDiff != 0 && progressRight - progressLeft < minProgressDiff) {
progressRight = progressLeft + minProgressDiff;
if (progressRight > 1.0f) {
progressRight = 1.0f;
}
}
if (delegate != null) {
delegate.onRightProgressChanged(progressRight);
}
invalidate();
return true;
}
}
return false;
}
public void setColor(int color) {
paint.setColor(color);
}
public void setVideoPath(String path) {
destroy();
mediaMetadataRetriever = new MediaMetadataRetriever();
progressLeft = 0.0f;
progressRight = 1.0f;
try {
mediaMetadataRetriever.setDataSource(path);
String duration = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
videoLength = Long.parseLong(duration);
} catch (Exception e) {
}
invalidate();
}
public void setDelegate(VideoTimelineViewDelegate delegate) {
this.delegate = delegate;
}
private void reloadFrames(int frameNum) {
if (mediaMetadataRetriever == null) {
return;
}
if (frameNum == 0) {
if (isRoundFrames) {
frameHeight = frameWidth = AndroidUtilities.dp(56);
framesToLoad = (int) Math.ceil((getMeasuredWidth() - AndroidUtilities.dp(16)) / (frameHeight / 2.0f));
} else {
frameHeight = getMeasuredHeight() - AndroidUtilities.dp(5);
framesToLoad = (getMeasuredWidth() - AndroidUtilities.dp(16)) / frameHeight;
frameWidth = (int) Math.ceil((float) (getMeasuredWidth() - AndroidUtilities.dp(16)) / (float) framesToLoad);
}
frameTimeOffset = videoLength / framesToLoad;
}
currentTask = new AsyncTask<Integer, Integer, Bitmap>() {
private int frameNum = 0;
@Override
protected Bitmap doInBackground(Integer... objects) {
frameNum = objects[0];
Bitmap bitmap = null;
if (isCancelled()) {
return null;
}
try {
bitmap = mediaMetadataRetriever.getFrameAtTime(frameTimeOffset * frameNum * 1000, MediaMetadataRetriever.OPTION_CLOSEST_SYNC);
if (isCancelled()) {
return null;
}
if (bitmap != null) {
Bitmap result = Bitmap.createBitmap(frameWidth, frameHeight, bitmap.getConfig());
Canvas canvas = new Canvas(result);
float scaleX = (float) frameWidth / (float) bitmap.getWidth();
float scaleY = (float) frameHeight / (float) bitmap.getHeight();
float scale = scaleX > scaleY ? scaleX : scaleY;
int w = (int) (bitmap.getWidth() * scale);
int h = (int) (bitmap.getHeight() * scale);
Rect srcRect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
Rect destRect = new Rect((frameWidth - w) / 2, (frameHeight - h) / 2, w, h);
canvas.drawBitmap(bitmap, srcRect, destRect, null);
bitmap.recycle();
bitmap = result;
}
} catch (Exception e) {
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
if (!isCancelled()) {
frames.add(bitmap);
invalidate();
if (frameNum < framesToLoad) {
reloadFrames(frameNum + 1);
}
}
}
};
currentTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, frameNum, null, null);
}
public void destroy() {
synchronized (sync) {
try {
if (mediaMetadataRetriever != null) {
mediaMetadataRetriever.release();
mediaMetadataRetriever = null;
}
} catch (Exception e) {
}
}
for (int a = 0; a < frames.size(); a++) {
Bitmap bitmap = frames.get(a);
if (bitmap != null) {
bitmap.recycle();
}
}
frames.clear();
if (currentTask != null) {
currentTask.cancel(true);
currentTask = null;
}
}
public void clearFrames() {
for (int a = 0; a < frames.size(); a++) {
Bitmap bitmap = frames.get(a);
if (bitmap != null) {
bitmap.recycle();
}
}
frames.clear();
if (currentTask != null) {
currentTask.cancel(true);
currentTask = null;
}
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
int width = getMeasuredWidth() - AndroidUtilities.dp(36);
int startX = (int) (width * progressLeft) + AndroidUtilities.dp(16);
int endX = (int) (width * progressRight) + AndroidUtilities.dp(16);
canvas.save();
canvas.clipRect(AndroidUtilities.dp(16), 0, width + AndroidUtilities.dp(20), getMeasuredHeight());
if (frames.isEmpty() && currentTask == null) {
reloadFrames(0);
} else {
int offset = 0;
for (int a = 0; a < frames.size(); a++) {
Bitmap bitmap = frames.get(a);
if (bitmap != null) {
int x = AndroidUtilities.dp(16) + offset * (isRoundFrames ? frameWidth / 2 : frameWidth);
int y = AndroidUtilities.dp(2);
if (isRoundFrames) {
rect2.set(x, y, x + AndroidUtilities.dp(28), y + AndroidUtilities.dp(28));
canvas.drawBitmap(bitmap, rect1, rect2, null);
} else {
canvas.drawBitmap(bitmap, x, y, null);
}
}
offset++;
}
}
int top = AndroidUtilities.dp(2);
canvas.drawRect(AndroidUtilities.dp(16), top, startX, getMeasuredHeight() - top, paint2);
canvas.drawRect(endX + AndroidUtilities.dp(4), top, AndroidUtilities.dp(16) + width + AndroidUtilities.dp(4), getMeasuredHeight() - top, paint2);
canvas.drawRect(startX, 0, startX + AndroidUtilities.dp(2), getMeasuredHeight(), paint);
canvas.drawRect(endX + AndroidUtilities.dp(2), 0, endX + AndroidUtilities.dp(4), getMeasuredHeight(), paint);
canvas.drawRect(startX + AndroidUtilities.dp(2), 0, endX + AndroidUtilities.dp(4), top, paint);
canvas.drawRect(startX + AndroidUtilities.dp(2), getMeasuredHeight() - top, endX + AndroidUtilities.dp(4), getMeasuredHeight(), paint);
canvas.restore();
canvas.drawCircle(startX + AndroidUtilities.dp(1), getMeasuredHeight() / 2, AndroidUtilities.dp(7), paint);
canvas.drawCircle(endX + AndroidUtilities.dp(3), getMeasuredHeight() / 2, AndroidUtilities.dp(7), paint);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment