Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0d3de13d authored by Denise Cheng's avatar Denise Cheng Committed by Steve Kondik
Browse files

webkit: Multimedia feature port from ICS

- Multi-context HTML5 video support
- Smooth inline to fullscreen video transition
- Support for autoplay, muted, loop, preload attributes
- Support for web pages with more than one audio element
- Support for HTTP live streaming

Change-Id: I82b01295da4663437d5c35eb45d145b243ecc9f1

webkit: HTML5 video user experience and stability fixes

- Fix HTML5 video unable to resume from navigation history
- Fix occasional crash when reloading page from history
- Show last decoded frame of streaming video when buffering
- Fix incorrect fullscreen video position during orientation change
- Replace HTML5VideoView resetted state with released state
- Remove unneeded videoLayerId in HTML5VideoView
- Resume from last played position after video suspend
- Call MediaController::show() during HTML5VideoView play and pause
  to update fullscreen MediaController state

Change-Id: I82f1255225b2ee6cc95f70e40017d562e5e635c6

webkit: Fix black screen shown during webkit exitFullscreen

Notify WebChromeClient to hide custom view during exit fullscreen.

CRs-fixed: 387484

Change-Id: If67cc000991d9fa0696692ce3982e0a04499e343
parent 8ac14f5a
Loading
Loading
Loading
Loading
+37 −5
Original line number Diff line number Diff line
/*
 * Copyright (C) 2010 The Android Open Source Project
 * Copyright (c) 2012, Code Aurora Forum. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
@@ -70,10 +71,15 @@ class HTML5Audio extends Handler
    private boolean mLoopEnabled = false;
    private boolean mProcessingOnEnd = false;
    private Context mContext;
    // The handler for WebCore thread messages;
    private Handler mWebCoreHandler;

    // Timer thread -> UI thread
    private static final int TIMEUPDATE = 100;

    // AudioManager callback thread -> Webcore thread
    private static final int AUDIOFOCUS_CHANGED = 200;

    private static final String COOKIE = "Cookie";
    private static final String HIDE_URL_LOGS = "x-hide-urls-from-log";

@@ -188,6 +194,7 @@ class HTML5Audio extends Handler
    public HTML5Audio(WebViewCore webViewCore, int nativePtr) {
        // Save the native ptr
        mNativePointer = nativePtr;
        createWebCoreHandler();
        resetMediaPlayer();
        mContext = webViewCore.getContext();
        mIsPrivateBrowsingEnabledGetter = new IsPrivateBrowsingEnabledGetter(
@@ -240,8 +247,7 @@ class HTML5Audio extends Handler
        }
    }

    @Override
    public void onAudioFocusChange(int focusChange) {
    private void handleAudioFocusChange(int focusChange) {
        switch (focusChange) {
        case AudioManager.AUDIOFOCUS_GAIN:
            // resume playback
@@ -254,10 +260,10 @@ class HTML5Audio extends Handler
            break;

        case AudioManager.AUDIOFOCUS_LOSS:
            // Lost focus for an unbounded amount of time: stop playback.
            // Lost focus for an unbounded amount of time: pause playback.
            if (mState != ERROR && mMediaPlayer.isPlaying()) {
                mMediaPlayer.stop();
                mState = STOPPED;
                pause();
                nativeOnPaused(mNativePointer);
            }
            break;

@@ -270,6 +276,25 @@ class HTML5Audio extends Handler
        }
    }

    private void createWebCoreHandler() {
        mWebCoreHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case AUDIOFOCUS_CHANGED:
                        handleAudioFocusChange(msg.arg1);
                        break;
                }
            }
        };
    }

    @Override
    public void onAudioFocusChange(int focusChange) {
        Message msg = Message.obtain(mWebCoreHandler, AUDIOFOCUS_CHANGED);
        msg.arg1 = focusChange;
        mWebCoreHandler.sendMessage(msg);
    }

    private void play() {
        if (mState == COMPLETE && mLoopEnabled == true) {
@@ -316,6 +341,12 @@ class HTML5Audio extends Handler
        }
    }

    private void setVolume(float volume) {
        if (mState >= PREPARED) {
            mMediaPlayer.setVolume(volume, volume);
        }
    }

    /**
     * Called only over JNI when WebKit is happy to
     * destroy the media player.
@@ -337,6 +368,7 @@ class HTML5Audio extends Handler

    private native void nativeOnBuffering(int percent, int nativePointer);
    private native void nativeOnEnded(int nativePointer);
    private native void nativeOnPaused(int nativePointer);
    private native void nativeOnRequestPlay(int nativePointer);
    private native void nativeOnPrepared(int duration, int width, int height, int nativePointer);
    private native void nativeOnTimeupdate(int position, int nativePointer);
+0 −391
Original line number Diff line number Diff line

package android.webkit;

import android.content.Context;
import android.media.MediaPlayer;
import android.media.Metadata;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.MediaController;
import android.widget.MediaController.MediaPlayerControl;


/**
 * @hide This is only used by the browser
 */
public class HTML5VideoFullScreen extends HTML5VideoView
    implements MediaPlayerControl, MediaPlayer.OnPreparedListener,
    View.OnTouchListener {

    // Add this sub-class to handle the resizing when rotating screen.
    private class VideoSurfaceView extends SurfaceView {

        public VideoSurfaceView(Context context) {
            super(context);
        }

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            int width = getDefaultSize(mVideoWidth, widthMeasureSpec);
            int height = getDefaultSize(mVideoHeight, heightMeasureSpec);
            if (mVideoWidth > 0 && mVideoHeight > 0) {
                if ( mVideoWidth * height  > width * mVideoHeight ) {
                    height = width * mVideoHeight / mVideoWidth;
                } else if ( mVideoWidth * height  < width * mVideoHeight ) {
                    width = height * mVideoWidth / mVideoHeight;
                }
            }
            setMeasuredDimension(width, height);
        }
    }

    // This view will contain the video.
    private VideoSurfaceView mVideoSurfaceView;

    // We need the full screen state to decide which surface to render to and
    // when to create the MediaPlayer accordingly.
    static final int FULLSCREEN_OFF               = 0;
    static final int FULLSCREEN_SURFACECREATING   = 1;
    static final int FULLSCREEN_SURFACECREATED    = 2;

    private int mFullScreenMode;
    // The Media Controller only used for full screen mode
    private MediaController mMediaController;

    // SurfaceHolder for full screen
    private SurfaceHolder mSurfaceHolder = null;

    // Data only for MediaController
    private boolean mCanSeekBack;
    private boolean mCanSeekForward;
    private boolean mCanPause;
    private int mCurrentBufferPercentage;

    // The progress view.
    private static View mProgressView;
    // The container for the progress view and video view
    private static FrameLayout mLayout;

    // The video size will be ready when prepared. Used to make sure the aspect
    // ratio is correct.
    private int mVideoWidth;
    private int mVideoHeight;

    SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback()
    {
        public void surfaceChanged(SurfaceHolder holder, int format,
                                    int w, int h)
        {
            if (mPlayer != null && mMediaController != null
                    && mCurrentState == STATE_PREPARED) {
                if (mMediaController.isShowing()) {
                    // ensure the controller will get repositioned later
                    mMediaController.hide();
                }
                mMediaController.show();
            }
        }

        public void surfaceCreated(SurfaceHolder holder)
        {
            mSurfaceHolder = holder;
            mFullScreenMode = FULLSCREEN_SURFACECREATED;

            prepareForFullScreen();
        }

        public void surfaceDestroyed(SurfaceHolder holder)
        {
            // After we return from this we can't use the surface any more.
            // The current Video View will be destroy when we play a new video.
            pauseAndDispatch(mProxy);
            // TODO: handle full screen->inline mode transition without a reload.
            mPlayer.release();
            mPlayer = null;
            mSurfaceHolder = null;
            if (mMediaController != null) {
                mMediaController.hide();
            }
        }
    };

    MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener =
        new MediaPlayer.OnVideoSizeChangedListener() {
            @Override
            public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
                mVideoWidth = mp.getVideoWidth();
                mVideoHeight = mp.getVideoHeight();
                if (mVideoWidth != 0 && mVideoHeight != 0) {
                    mVideoSurfaceView.getHolder().setFixedSize(mVideoWidth, mVideoHeight);
                }
            }
    };

    private SurfaceView getSurfaceView() {
        return mVideoSurfaceView;
    }

    HTML5VideoFullScreen(Context context, int videoLayerId, int position, boolean skipPrepare) {
        mVideoSurfaceView = new VideoSurfaceView(context);
        mFullScreenMode = FULLSCREEN_OFF;
        mVideoWidth = 0;
        mVideoHeight = 0;
        init(videoLayerId, position, skipPrepare);
    }

    private void setMediaController(MediaController m) {
        mMediaController  = m;
        attachMediaController();
    }

    private void attachMediaController() {
        if (mPlayer != null && mMediaController != null) {
            mMediaController.setMediaPlayer(this);
            mMediaController.setAnchorView(mVideoSurfaceView);
            //Will be enabled when prepared
            mMediaController.setEnabled(false);
        }
    }

    @Override
    public void decideDisplayMode() {
        mPlayer.setDisplay(mSurfaceHolder);
    }

    private void prepareForFullScreen() {
        MediaController mc = new FullScreenMediaController(mProxy.getContext(), mLayout);
        mc.setSystemUiVisibility(mLayout.getSystemUiVisibility());
        setMediaController(mc);
        mPlayer.setScreenOnWhilePlaying(true);
        mPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
        prepareDataAndDisplayMode(mProxy);
    }


    private void toggleMediaControlsVisiblity() {
        if (mMediaController.isShowing()) {
            mMediaController.hide();
        } else {
            mMediaController.show();
        }
    }

    @Override
    public void onPrepared(MediaPlayer mp) {
        super.onPrepared(mp);

        mVideoSurfaceView.setOnTouchListener(this);
        // Get the capabilities of the player for this stream
        Metadata data = mp.getMetadata(MediaPlayer.METADATA_ALL,
                MediaPlayer.BYPASS_METADATA_FILTER);
        if (data != null) {
            mCanPause = !data.has(Metadata.PAUSE_AVAILABLE)
                    || data.getBoolean(Metadata.PAUSE_AVAILABLE);
            mCanSeekBack = !data.has(Metadata.SEEK_BACKWARD_AVAILABLE)
                    || data.getBoolean(Metadata.SEEK_BACKWARD_AVAILABLE);
            mCanSeekForward = !data.has(Metadata.SEEK_FORWARD_AVAILABLE)
                    || data.getBoolean(Metadata.SEEK_FORWARD_AVAILABLE);
        } else {
            mCanPause = mCanSeekBack = mCanSeekForward = true;
        }

        if (mProgressView != null) {
            mProgressView.setVisibility(View.GONE);
        }

        mVideoWidth = mp.getVideoWidth();
        mVideoHeight = mp.getVideoHeight();
        // This will trigger the onMeasure to get the display size right.
        mVideoSurfaceView.getHolder().setFixedSize(mVideoWidth, mVideoHeight);
        // Call into the native to ask for the state, if still in play mode,
        // this will trigger the video to play.
        mProxy.dispatchOnRestoreState();

        if (getStartWhenPrepared()) {
            mPlayer.start();
            // Clear the flag.
            setStartWhenPrepared(false);
        }

        // mMediaController status depends on the Metadata result, so put it
        // after reading the MetaData.
        // And make sure mPlayer state is updated before showing the controller.
        if (mMediaController != null) {
            mMediaController.setEnabled(true);
            mMediaController.show();
        }
    }

    public boolean fullScreenExited() {
        return (mLayout == null);
    }

    private final WebChromeClient.CustomViewCallback mCallback =
        new WebChromeClient.CustomViewCallback() {
            public void onCustomViewHidden() {
                // It listens to SurfaceHolder.Callback.SurfaceDestroyed event
                // which happens when the video view is detached from its parent
                // view. This happens in the WebChromeClient before this method
                // is invoked.
                mProxy.dispatchOnStopFullScreen();
                mLayout.removeView(getSurfaceView());

                if (mProgressView != null) {
                    mLayout.removeView(mProgressView);
                    mProgressView = null;
                }
                mLayout = null;
                // Re enable plugin views.
                mProxy.getWebView().getViewManager().showAll();

                mProxy = null;

                // Don't show the controller after exiting the full screen.
                mMediaController = null;
                mCurrentState = STATE_RESETTED;
            }
        };

    @Override
    public void enterFullScreenVideoState(int layerId,
            HTML5VideoViewProxy proxy, WebViewClassic webView) {
        mFullScreenMode = FULLSCREEN_SURFACECREATING;
        mCurrentBufferPercentage = 0;
        mPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);
        mProxy = proxy;

        mVideoSurfaceView.getHolder().addCallback(mSHCallback);
        mVideoSurfaceView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        mVideoSurfaceView.setFocusable(true);
        mVideoSurfaceView.setFocusableInTouchMode(true);
        mVideoSurfaceView.requestFocus();

        // Create a FrameLayout that will contain the VideoView and the
        // progress view (if any).
        mLayout = new FrameLayout(mProxy.getContext());
        FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
                            ViewGroup.LayoutParams.WRAP_CONTENT,
                            ViewGroup.LayoutParams.WRAP_CONTENT,
                            Gravity.CENTER);

        mLayout.addView(getSurfaceView(), layoutParams);

        mLayout.setVisibility(View.VISIBLE);
        WebChromeClient client = webView.getWebChromeClient();
        if (client != null) {
            client.onShowCustomView(mLayout, mCallback);
            // Plugins like Flash will draw over the video so hide
            // them while we're playing.
            if (webView.getViewManager() != null)
                webView.getViewManager().hideAll();

            mProgressView = client.getVideoLoadingProgressView();
            if (mProgressView != null) {
                mLayout.addView(mProgressView, layoutParams);
                mProgressView.setVisibility(View.VISIBLE);
            }
        }
    }

    /**
     * @return true when we are in full screen mode, even the surface not fully
     * created.
     */
    public boolean isFullScreenMode() {
        return true;
    }

    // MediaController FUNCTIONS:
    @Override
    public boolean canPause() {
        return mCanPause;
    }

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

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

    @Override
    public int getBufferPercentage() {
        if (mPlayer != null) {
            return mCurrentBufferPercentage;
        }
    return 0;
    }

    @Override
    public void showControllerInFullScreen() {
        if (mMediaController != null) {
            mMediaController.show(0);
        }
    }

    // Other listeners functions:
    private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener =
        new MediaPlayer.OnBufferingUpdateListener() {
        public void onBufferingUpdate(MediaPlayer mp, int percent) {
            mCurrentBufferPercentage = percent;
        }
    };

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (mFullScreenMode >= FULLSCREEN_SURFACECREATED
                && mMediaController != null) {
            toggleMediaControlsVisiblity();
        }
        return false;
    }

    @Override
    protected void switchProgressView(boolean playerBuffering) {
        if (mProgressView != null) {
            if (playerBuffering) {
                mProgressView.setVisibility(View.VISIBLE);
            } else {
                mProgressView.setVisibility(View.GONE);
            }
        }
        return;
    }

    static class FullScreenMediaController extends MediaController {

        View mVideoView;

        public FullScreenMediaController(Context context, View video) {
            super(context);
            mVideoView = video;
        }

        @Override
        public void show() {
            super.show();
            if (mVideoView != null) {
                mVideoView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
            }
        }

        @Override
        public void hide() {
            if (mVideoView != null) {
                mVideoView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE
                        | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
            }
            super.hide();
        }

    }

}
+0 −116
Original line number Diff line number Diff line

package android.webkit;

import android.Manifest.permission;
import android.content.pm.PackageManager;
import android.graphics.SurfaceTexture;
import android.media.MediaPlayer;
import android.webkit.HTML5VideoView;
import android.webkit.HTML5VideoViewProxy;
import android.view.Surface;
import android.opengl.GLES20;
import android.os.PowerManager;

/**
 * @hide This is only used by the browser
 */
public class HTML5VideoInline extends HTML5VideoView{

    // Due to the fact that the decoder consume a lot of memory, we make the
    // surface texture as singleton. But the GL texture (m_textureNames)
    // associated with the surface texture can be used for showing the screen
    // shot when paused, so they are not singleton.
    private static SurfaceTexture mSurfaceTexture = null;
    private static int[] mTextureNames = null;
    // Every time when the VideoLayer Id change, we need to recreate the
    // SurfaceTexture in order to delete the old video's decoder memory.
    private static int mVideoLayerUsingSurfaceTexture = -1;

    // Video control FUNCTIONS:
    @Override
    public void start() {
        if (!getPauseDuringPreparing()) {
            super.start();
        }
    }

    HTML5VideoInline(int videoLayerId, int position) {
        init(videoLayerId, position, false);
    }

    @Override
    public void decideDisplayMode() {
        SurfaceTexture surfaceTexture = getSurfaceTexture(getVideoLayerId());
        Surface surface = new Surface(surfaceTexture);
        mPlayer.setSurface(surface);
        surface.release();
    }

    // Normally called immediately after setVideoURI. But for full screen,
    // this should be after surface holder created
    @Override
    public void prepareDataAndDisplayMode(HTML5VideoViewProxy proxy) {
        super.prepareDataAndDisplayMode(proxy);
        setFrameAvailableListener(proxy);
        // TODO: This is a workaround, after b/5375681 fixed, we should switch
        // to the better way.
        if (mProxy.getContext().checkCallingOrSelfPermission(permission.WAKE_LOCK)
                == PackageManager.PERMISSION_GRANTED) {
            mPlayer.setWakeMode(proxy.getContext(), PowerManager.FULL_WAKE_LOCK);
        }
    }

    // Pause the play and update the play/pause button
    @Override
    public void pauseAndDispatch(HTML5VideoViewProxy proxy) {
        super.pauseAndDispatch(proxy);
    }

    // Inline Video specific FUNCTIONS:

    public static SurfaceTexture getSurfaceTexture(int videoLayerId) {
        // Create the surface texture.
        if (videoLayerId != mVideoLayerUsingSurfaceTexture
            || mSurfaceTexture == null
            || mTextureNames == null) {
            // The GL texture will store in the VideoLayerManager at native side.
            // They will be clean up when requested.
            // The reason we recreated GL texture name is for screen shot support.
            mTextureNames = new int[1];
            GLES20.glGenTextures(1, mTextureNames, 0);
            mSurfaceTexture = new SurfaceTexture(mTextureNames[0]);
        }
        mVideoLayerUsingSurfaceTexture = videoLayerId;
        return mSurfaceTexture;
    }

    public boolean surfaceTextureDeleted() {
        return (mSurfaceTexture == null);
    }

    @Override
    public void deleteSurfaceTexture() {
        cleanupSurfaceTexture();
        return;
    }

    public static void cleanupSurfaceTexture() {
        mSurfaceTexture = null;
        mVideoLayerUsingSurfaceTexture = -1;
        return;
    }

    @Override
    public int getTextureName() {
        if (mTextureNames != null) {
            return mTextureNames[0];
        } else {
            return 0;
        }
    }

    private void setFrameAvailableListener(SurfaceTexture.OnFrameAvailableListener l) {
        mSurfaceTexture.setOnFrameAvailableListener(l);
    }

}
+619 −139

File changed.

Preview size limit exceeded, changes collapsed.

+108 −0
Original line number Diff line number Diff line
/* Copyright (c) 2011, Code Aurora Forum. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 *       copyright notice, this list of conditions and the following
 *       disclaimer in the documentation and/or other materials provided
 *       with the distribution.
 *     * Neither the name of Code Aurora Forum, Inc. nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package android.webkit;

import java.util.ArrayList;
import java.util.Iterator;

/**
 * @hide This is only used by the browser. Manager for HTML5 video views.
 */
class HTML5VideoViewManager
{
    private WebViewClassic mWebView;
    private ArrayList<HTML5VideoViewProxy> mProxyList;
    private final Thread mUiThread;

    public HTML5VideoViewManager(WebViewClassic webView) {
        mWebView = webView;
        mUiThread = Thread.currentThread();
        mProxyList = new ArrayList<HTML5VideoViewProxy>();
    }

    public boolean registerProxy(HTML5VideoViewProxy proxy) {
        assert (mUiThread == Thread.currentThread());
        boolean result = mProxyList.add(proxy);
        return result;
    }

    public boolean unregisterProxy(HTML5VideoViewProxy proxy) {
        assert (mUiThread == Thread.currentThread());
        boolean result = mProxyList.remove(proxy);
        return result;
    }

    public void setBaseLayer(int layer) {
        assert (mUiThread == Thread.currentThread());
        Iterator<HTML5VideoViewProxy> iter = mProxyList.iterator();
        while (iter.hasNext()) {
            HTML5VideoViewProxy proxy = iter.next();
            proxy.setBaseLayer(layer);
        }
    }

    public void suspend() {
        assert (mUiThread == Thread.currentThread());
        Iterator<HTML5VideoViewProxy> iter = mProxyList.iterator();
        while (iter.hasNext()) {
            HTML5VideoViewProxy proxy = iter.next();
            proxy.suspend();
        }
    }

    public void pauseAndDispatch() {
        assert (mUiThread == Thread.currentThread());
        Iterator<HTML5VideoViewProxy> iter = mProxyList.iterator();
        while (iter.hasNext()) {
            HTML5VideoViewProxy proxy = iter.next();
            proxy.pauseAndDispatch();
        }
    }

    public void enterFullscreenVideo(int layerId, String url) {
        assert (mUiThread == Thread.currentThread());
        Iterator<HTML5VideoViewProxy> iter = mProxyList.iterator();
        while (iter.hasNext()) {
            HTML5VideoViewProxy proxy = iter.next();
            if (proxy.getVideoLayerId() == layerId)
                proxy.webkitEnterFullscreen();
            else
                proxy.pauseAndDispatch();
        }
    }

    public void exitFullscreenVideo() {
        assert (mUiThread == Thread.currentThread());
        Iterator<HTML5VideoViewProxy> iter = mProxyList.iterator();
        while (iter.hasNext()) {
            HTML5VideoViewProxy proxy = iter.next();
            proxy.webKitExitFullscreen();
        }
    }
}
Loading