FATAL EXCEPTION: JavaBridge - Only the original thread that created a view hierarchy can touch its views

1.1k Views Asked by At

I'm developing the cordova plugin for playing Media.(Video)

I try to play video using mediaPlayer Class for Android.

There was no error, I succeeded to the prepare step.

But in "onPrepared" function, i try to use "mediaController.show() & mediaPlayer.start()" api,

There is error [logcat] :

E/AndroidRuntime: FATAL EXCEPTION: JavaBridge
                  Process: yongju.example.media, PID: 3779
                  android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
                      at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6891)
                      at android.view.ViewRootImpl.recomputeViewAttributes(ViewRootImpl.java:3247)
                      at android.view.ViewGroup.recomputeViewAttributes(ViewGroup.java:1324)
                      at android.view.ViewGroup.recomputeViewAttributes(ViewGroup.java:1324)
                      at android.view.ViewGroup.recomputeViewAttributes(ViewGroup.java:1324)
                      at android.view.View.setFlags(View.java:11580)
                      at android.view.View.setKeepScreenOn(View.java:7554)
                      at android.view.SurfaceView$1.handleMessage(SurfaceView.java:135)
                      at android.os.Handler.dispatchMessage(Handler.java:102)
                      at android.os.Looper.loop(Looper.java:154)
                      at android.os.HandlerThread.run(HandlerThread.java:61)

There are my codes [cordova plugin] :

package com.cordova.plugin.toast;


import org.apache.cordova.CordovaWebView;
import org.json.JSONArray;
import org.json.JSONException;

import android.content.Context;

import android.media.AudioManager;
import android.net.Uri;
import android.os.Looper;
import android.util.Log;

import org.apache.cordova.CallbackContext;

import org.apache.cordova.CordovaActivity;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPlugin;

import android.view.View;
import android.widget.FrameLayout;
import android.widget.MediaController;

import android.media.MediaPlayer;
import android.view.SurfaceHolder;

import android.os.Handler;

import android.widget.VideoView;

import java.io.IOException;

public class Media extends CordovaPlugin implements SurfaceHolder.Callback, MediaPlayer.OnPreparedListener, MediaController.MediaPlayerControl {

    class MediaPlayerState {
        protected String NONE = "NONE";
        protected String IDLE = "IDLE";
        protected String READY = "READY"; // prepared
        protected String PLAYING = "PLAYING";
        protected String PAUSED = "PAUSED";
    }

    private MediaPlayerState mPlayerState = new MediaPlayerState();
    private static final String TAG = "Media";

    private FrameLayout fraLayout;
    private CordovaWebView webView;

    private MediaPlayer mediaPlayer;
    private MediaController mcontroller;

    private VideoView videoView;
    private SurfaceHolder surfaceHolder;
    private String src, state;

    private CordovaActivity nowActivity;
    private Context context;

    private CallbackContext callbackContext;

    @Override
    public void initialize(CordovaInterface cordova, CordovaWebView webView) {
        super.initialize(cordova, webView);
        this.webView = webView;
        nowActivity = (CordovaActivity)this.cordova.getActivity();
        context = this.cordova.getActivity().getApplicationContext();
    }

    @Override
    public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) throws JSONException {
        this.callbackContext = callbackContext;
        if (action.equals("create")) {
            Log.v(TAG, "**************Success 'create'**************");

            createVideoContainer();
        }

        else if (action.equals("open")) {
            Log.v(TAG, "**************Success 'open'**************");

            openVideoStream(args);
        }

        else if (action.equals("play")) {
            Log.v(TAG, "**************Success 'play'**************");

            playVideoStream(args);
        }

        else if (action.equals("stop")) {
            Log.v(TAG, "**************Success 'stop'**************");

            stopVideoStream(args);
        }

        else if (action.equals("pause")) {
            Log.v(TAG, "**************Success 'pause'**************");

            pauseVideoStream(args);
        }

        else {
            return false;
        }

        return true;
    }

    private void createVideoContainer() {
        state = mPlayerState.NONE;

        fraLayout = (FrameLayout) this.webView.getView().getParent();
        FrameLayout.LayoutParams fraLayoutParam = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT);
        fraLayoutParam.setMargins(80, 80, 80, 80);

        videoView = new VideoView(cordova.getActivity());
        videoView.setLayoutParams(fraLayoutParam);
        videoView.setVisibility(View.VISIBLE);

        surfaceHolder = videoView.getHolder();

        surfaceHolder.addCallback(this);

        mcontroller = new MediaController(this.cordova.getActivity());
    }

    private void openVideoStream(JSONArray args) throws JSONException {
        String id = args.getString(0);
        src = args.getString(1);
        Log.v(TAG, "media::open() - id =" + id);
    }

    private void playVideoStream(JSONArray args) throws JSONException {
        String id = args.getString(0);
        Log.v(TAG, "media::play() - id =" + id);

        if(state == mPlayerState.IDLE) {
            // If 'prepared' is not completed, must again 'prepare'
        }
        else{
            mediaPlayer.start();
            state = mPlayerState.PLAYING;
        }
    }

    private void pauseVideoStream(JSONArray args) throws JSONException {
        String id = args.getString(0);
        Log.v(TAG, "media::pause() - EVENT_STATE -> PAUSED");

        mediaPlayer.pause();
        state = mPlayerState.PAUSED;
    }

    private void stopVideoStream(JSONArray args) throws JSONException {
        String id = args.getString(0);
        Log.v(TAG, "media::stop() - EVENT_STATE -> IDLE");

        mediaPlayer.stop();
        state = mPlayerState.IDLE;
        //videoView.setVisibility(View.INVISIBLE);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        Log.v(TAG, "*********************************************************media::surfaceCreated - Start MediaPlayer Object create : " + src);
        if (mediaPlayer == null) {
            mediaPlayer = new MediaPlayer();
        }
        else {
            mediaPlayer.reset();
        }

        state = mPlayerState.IDLE;

        try {
            String fileName = "android.resource://yongju.example.media/raw/sample";
            Uri fileUri = Uri.parse(fileName);

            mediaPlayer.setDataSource(context, fileUri); // for testing In local file
            mediaPlayer.setDisplay(holder);      // Call Screen
        } catch (IOException e) {
            e.printStackTrace();
        }

        mediaPlayer.setOnCompletionListener(completionListener);
        mediaPlayer.setOnVideoSizeChangedListener(sizeChangeListener);
        mediaPlayer.setOnPreparedListener(this);
        mediaPlayer.setScreenOnWhilePlaying(true);
        //mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);

        mediaPlayer.prepareAsync();                      // Ready for loading Video
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        Log.v(TAG, "*********************************************************media::surfaceChanged");
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        Log.v(TAG, "*********************************************************media::surfaceDestroyed");
        mediaPlayer.release();
    }

    MediaPlayer.OnCompletionListener completionListener = new MediaPlayer.OnCompletionListener() {
        /**
         * Listener for playing video is completed
         * @param mediaPlayer : Now mediaPlayer that is controlled
         */
        @Override
        public void onCompletion(MediaPlayer mediaPlayer) {
        }
    };

    MediaPlayer.OnVideoSizeChangedListener sizeChangeListener = new MediaPlayer.OnVideoSizeChangedListener() {
        @Override
        public void onVideoSizeChanged(MediaPlayer mediaPlayer, int width, int height) {
        }
    };

    @Override
    public void onPrepared(MediaPlayer mp) {
        Log.v(TAG, "*********************************************************media::prepare completed");
        state = mPlayerState.READY;
        mcontroller.setMediaPlayer(this);
        mcontroller.setAnchorView(videoView);
        mcontroller.setEnabled(true);

        cordova.getActivity().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if (cordova.getActivity().isFinishing() == false) {
                    mcontroller.show();
                    mp.start();
                }

            }
        });
    }

    @Override
    public void start() {
        mediaPlayer.start();
    }

    @Override
    public void pause() {
        mediaPlayer.pause();
    }

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

    @Override
    public int getCurrentPosition() {
        return mediaPlayer.getDuration();
    }

    @Override
    public void seekTo(int pos) {
        mediaPlayer.seekTo(pos);
    }

    @Override
    public boolean isPlaying() {
        return mediaPlayer.isPlaying();
    }

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

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

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

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

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

        @Override
        public void seekTo(int pos) {
            mediaPlayer.seekTo(pos);
        }

        @Override
        public boolean isPlaying() {
            return mediaPlayer.isPlaying();
        }

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

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

        @Override
        public boolean canSeekBackward() {


    return true;
    }

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

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

Plz tell me why.

2

There are 2 best solutions below

1
On

You are trying to start MediaPlayer not from the main thread. Just call runOnUiThread function and everything should be alright.

0
On
activity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                //your implementetion
            }
        });