com.shahenlibrary.VideoPlayer.VideoPlayerView.java Source code

Java tutorial

Introduction

Here is the source code for com.shahenlibrary.VideoPlayer.VideoPlayerView.java

Source

/*
 * MIT License
 *
 * Copyright (c) 2017 Shahen Hovhannisyan.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.shahenlibrary.VideoPlayer;

import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.util.Base64;
import android.util.Log;
import android.widget.MediaController;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.events.RCTEventEmitter;
import com.shahenlibrary.interfaces.OnTrimVideoListener;
import com.shahenlibrary.utils.VideoEdit;
import com.yqritc.scalablevideoview.ScalableType;
import com.yqritc.scalablevideoview.ScalableVideoView;
import com.yqritc.scalablevideoview.ScaleManager;
import com.yqritc.scalablevideoview.Size;
import com.shahenlibrary.Events.Events;
import com.shahenlibrary.Events.EventsEnum;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;

import wseemann.media.FFmpegMediaMetadataRetriever;

public class VideoPlayerView extends ScalableVideoView implements MediaPlayer.OnPreparedListener,
        MediaPlayer.OnErrorListener, MediaPlayer.OnBufferingUpdateListener, MediaPlayer.OnCompletionListener,
        MediaPlayer.OnInfoListener, LifecycleEventListener, MediaController.MediaPlayerControl {

    private ThemedReactContext themedReactContext;
    private RCTEventEmitter eventEmitter;
    private String mediaSource;
    private boolean mPlay = true;
    private String LOG_TAG = "RNVideoProcessing";
    private Runnable progressRunnable = null;
    private Handler progressUpdateHandler = new Handler();
    private FFmpegMediaMetadataRetriever metadataRetriever = new FFmpegMediaMetadataRetriever();
    private int progressUpdateHandlerDelay = 1000;
    private int videoStartAt = 0;
    private int videoEndAt = -1;
    private boolean mLooping = false;
    private float mVolume = 10f;

    public VideoPlayerView(ThemedReactContext ctx) {
        super(ctx);

        themedReactContext = ctx;
        eventEmitter = themedReactContext.getJSModule(RCTEventEmitter.class);
        themedReactContext.addLifecycleEventListener(this);
        setSurfaceTextureListener(this);
        initPlayerIfNeeded();
        progressRunnable = new Runnable() {
            @Override
            public void run() {
                if (mMediaPlayer != null && mMediaPlayer.isPlaying()) {
                    if (mMediaPlayer.getCurrentPosition() >= videoEndAt && videoEndAt != -1) {
                        Log.d(LOG_TAG, "run: End time reached");
                        mMediaPlayer.seekTo(videoStartAt);
                        if (!mLooping) {
                            Log.d(LOG_TAG, "run: Pause video, no looping");
                            pause();
                        }
                    }
                    // Log.d(LOG_TAG, "run: onChange videoStartAt: " + videoStartAt + " endAt " + videoEndAt + " currentPosition: " + mMediaPlayer.getCurrentPosition() + " duration: " + mMediaPlayer.getDuration());
                    WritableMap event = Arguments.createMap();
                    event.putDouble(Events.CURRENT_TIME, mMediaPlayer.getCurrentPosition() / 1000);
                    eventEmitter.receiveEvent(getId(), EventsEnum.EVENT_PROGRESS.toString(), event);
                }

                progressUpdateHandler.postDelayed(progressRunnable, progressUpdateHandlerDelay);
            }
        };

        progressUpdateHandler.post(progressRunnable);
    }

    private void initPlayerIfNeeded() {
        if (mMediaPlayer != null) {
            return;
        }
        Log.d(LOG_TAG, "initPlayerIfNeeded");
        mMediaPlayer = new MediaPlayer();
        mMediaPlayer.setScreenOnWhilePlaying(true);
        mMediaPlayer.setOnVideoSizeChangedListener(this);
        mMediaPlayer.setOnErrorListener(this);
        mMediaPlayer.setOnPreparedListener(this);
        mMediaPlayer.setOnBufferingUpdateListener(this);
        mMediaPlayer.setOnCompletionListener(this);
        mMediaPlayer.setOnInfoListener(this);
    }

    public void setSource(final String uriString) {
        if (mediaSource != null && mediaSource.equals(uriString)) {
            return;
        }
        if (mMediaPlayer == null) {
            Log.d(LOG_TAG, "setSource: Media player is null");
            return;
        }
        reset();

        mediaSource = uriString;
        Log.d(LOG_TAG, "set source: " + mediaSource);

        initPlayerIfNeeded();

        try {
            if (VideoEdit.shouldUseURI(uriString)) {
                Uri parsedUri = Uri.parse(mediaSource);
                setDataSource(themedReactContext, parsedUri);
                metadataRetriever.setDataSource(themedReactContext, parsedUri);
            } else {
                setDataSource(mediaSource);
                metadataRetriever.setDataSource(mediaSource);
            }
            prepare(this);

            if (mPlay && !mMediaPlayer.isPlaying()) {
                Log.d(LOG_TAG, "setSource: Start video at once");
                start();
            }

        } catch (IOException e) {
            e.printStackTrace();
            Log.d(LOG_TAG, "setSrc: ERROR");
        }
    }

    public void setPlay(final boolean shouldPlay) {
        Log.d(LOG_TAG, "setPlay: " + shouldPlay);
        mPlay = shouldPlay;

        if (mMediaPlayer == null) {
            Log.d(LOG_TAG, "setPlay: Player reference is null");
            return;
        }
        if (shouldPlay && !mMediaPlayer.isPlaying()) {
            start();
            Log.d(LOG_TAG, "setPlay: START");
        }
        if (!shouldPlay && mMediaPlayer.isPlaying()) {
            pause();
            Log.d(LOG_TAG, "setPlay: PAUSE");
        }
    }

    public void setMediaVolume(float volume) {
        if (volume < 0) {
            return;
        }
        mVolume = volume;
        if (mMediaPlayer == null) {
            return;
        }
        setVolume(volume, volume);
    }

    public void setCurrentTime(float currentTime) {
        float seekTime = currentTime * 1000;
        if (mMediaPlayer == null) {
            Log.d(LOG_TAG, "MEDIA PLAYER IS NULL");
            return;
        }
        int duration = getDuration();
        if (currentTime > duration || currentTime < 0) {
            seekTime = 0;
        }
        Log.d(LOG_TAG, "set seek to " + String.valueOf((int) seekTime));
        seekTo((int) seekTime);
    }

    public void setRepeat(boolean repeat) {
        mLooping = repeat;

        if (mMediaPlayer == null) {
            return;
        }
        mMediaPlayer.setLooping(mLooping);
    }

    private void applyProps() {
        if (mMediaPlayer == null) {
            Log.d(LOG_TAG, "applyProps: MediaPlayer is null");
        }
        if (!mMediaPlayer.isLooping()) {
            mMediaPlayer.setLooping(mLooping);
        }
        if (mPlay && !isPlaying()) {
            mMediaPlayer.start();
        }
        mMediaPlayer.setVolume(mVolume, mVolume);
    }

    public void setVideoEndAt(int endAt) {
        videoEndAt = endAt * 1000;
        if (mMediaPlayer == null) {
            return;
        }
        if (videoEndAt > mMediaPlayer.getDuration()) {
            videoEndAt = mMediaPlayer.getDuration();
        }
        if (mMediaPlayer.getCurrentPosition() > endAt) {
            mMediaPlayer.seekTo(videoStartAt);
        }

        Log.d(LOG_TAG, "setVideoEndAt: " + videoEndAt);
    }

    public void cleanup() {
        if (mMediaPlayer == null) {
            return;
        }
        /* Cleanup the media player */
        mMediaPlayer.stop();
        mMediaPlayer.release();
        mMediaPlayer = null;
        /* Cleanup the media retriever */
        metadataRetriever.release();
        metadataRetriever = null;
    }

    public void setVideoStartAt(int startAt) {
        videoStartAt = startAt * 1000;
        if (mMediaPlayer == null) {
            return;
        }
        if (mMediaPlayer.getDuration() < videoStartAt) {
            mMediaPlayer.seekTo(videoStartAt);
        }
    }

    public void setProgressUpdateHandlerDelay(int delay) {
        this.progressUpdateHandlerDelay = delay;
    }

    public void sendMediaInfo() {
        if (mMediaPlayer == null) {
            Log.d(LOG_TAG, "sendMediaInfo: media Player is null");
            return;
        }

        WritableMap event = Arguments.createMap();

        int videoWidth = Integer
                .parseInt(metadataRetriever.extractMetadata(FFmpegMediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
        int videoHeight = Integer.parseInt(
                metadataRetriever.extractMetadata(FFmpegMediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));

        event.putInt(Events.DURATION, mMediaPlayer.getDuration() / 1000);
        event.putInt(Events.WIDTH, videoWidth);
        event.putInt(Events.HEIGHT, videoHeight);

        eventEmitter.receiveEvent(getId(), EventsEnum.EVENT_GET_INFO.toString(), event);
    }

    public void getFrame(float sec) {
        Bitmap bmp = metadataRetriever.getFrameAtTime((long) (sec * 1000000));
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        bmp.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream);
        byte[] byteArray = byteArrayOutputStream.toByteArray();
        String encoded = Base64.encodeToString(byteArray, Base64.DEFAULT);

        WritableMap event = Arguments.createMap();
        event.putString("image", encoded);

        eventEmitter.receiveEvent(getId(), EventsEnum.EVENT_GET_PREVIEW_IMAGE.toString(), event);
    }

    public void trimMedia(@Nullable double startMs, double endMs) {
        OnTrimVideoListener trimVideoListener = new OnTrimVideoListener() {
            @Override
            public void onError(String message) {
                Log.d(LOG_TAG, "Trimmed onError: " + message);
                WritableMap event = Arguments.createMap();
                event.putString(Events.ERROR_TRIM, message);

                eventEmitter.receiveEvent(getId(), EventsEnum.EVENT_GET_TRIMMED_SOURCE.toString(), event);
            }

            @Override
            public void onTrimStarted() {
                Log.d(LOG_TAG, "Trimmed onTrimStarted");
            }

            @Override
            public void getResult(Uri uri) {
                Log.d(LOG_TAG, "getResult: " + uri.toString());
                WritableMap event = Arguments.createMap();
                event.putString("source", uri.toString());
                eventEmitter.receiveEvent(getId(), EventsEnum.EVENT_GET_TRIMMED_SOURCE.toString(), event);
            }

            @Override
            public void cancelAction() {
                Log.d(LOG_TAG, "Trimmed cancelAction");
            }
        };

        Log.d(LOG_TAG, "trimMedia at : startAt -> " + startMs + " : endAt -> " + endMs);
        File mediaFile = new File(mediaSource.replace("file:///", "/"));
        long startTrimFromPos = (long) startMs * 1000;
        long endTrimFromPos = (long) endMs * 1000;
        String[] dPath = mediaSource.split("/");
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < dPath.length; ++i) {
            if (i == dPath.length - 1) {
                continue;
            }
            builder.append(dPath[i]);
            builder.append(File.separator);
        }
        String path = builder.toString().replace("file:///", "/");

        Log.d(LOG_TAG, "trimMedia: " + mediaFile.toString() + " isExists: " + mediaFile.exists());
        try {
            VideoEdit.startTrim(mediaFile, path, startTrimFromPos, endTrimFromPos, trimVideoListener);
        } catch (IOException e) {
            trimVideoListener.onError(e.toString());
            e.printStackTrace();
            Log.d(LOG_TAG, "trimMedia: error -> " + e.toString());
        }
    }

    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
        return false;
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        int videoWidth = getVideoWidth();
        int videoHeight = getVideoHeight();

        if (videoWidth == 0 || videoHeight == 0) {
            return;
        }
        Size viewSize = new Size(getWidth(), getHeight());
        Size videoSize = new Size(videoWidth, videoHeight);
        ScaleManager scaleManager = new ScaleManager(viewSize, videoSize);

        Matrix matrix = scaleManager.getScaleMatrix(mScalableType);
        if (matrix != null) {
            Log.d(LOG_TAG, "set transform");
            setTransform(matrix);
        }
    }

    @Override
    public void onPrepared(MediaPlayer mp) {
        videoEndAt = mp.getDuration();
        setScalableType(ScalableType.FIT_XY);
        invalidate();

        applyProps();
    }

    @Override
    public void onBufferingUpdate(MediaPlayer mp, int percent) {

    }

    @Override
    public void onCompletion(MediaPlayer mp) {
        if (mp == null) {
            return;
        }
        Log.d(LOG_TAG, "onCompletion: isLooping " + mp.isLooping());
        if (mLooping) {
            Log.d(LOG_TAG, "onCompletion: seek to start at : " + videoStartAt);
            mp.seekTo(videoStartAt);
        }
    }

    @Override
    public boolean onInfo(MediaPlayer mp, int what, int extra) {
        return false;
    }

    @Override
    public void onHostResume() {

    }

    @Override
    public void onHostPause() {

    }

    @Override
    public void onHostDestroy() {

    }

    @Override
    public int getBufferPercentage() {
        return 0;
    }

    @Override
    public boolean canPause() {
        return false;
    }

    @Override
    public boolean canSeekBackward() {
        return false;
    }

    @Override
    public boolean canSeekForward() {
        return false;
    }

    @Override
    public int getAudioSessionId() {
        return 0;
    }
}