1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
|
package android.webkit;
import android.graphics.SurfaceTexture;
import android.media.MediaPlayer;
import android.util.Log;
import android.webkit.HTML5VideoViewProxy;
import android.widget.MediaController;
import android.opengl.GLES20;
import java.io.IOException;
import java.util.Map;
/**
* @hide This is only used by the browser
*/
public class HTML5VideoView implements MediaPlayer.OnPreparedListener{
// Due to the fact that SurfaceTexture consume a lot of memory, we make it
// as static. m_textureNames is the texture bound with this SurfaceTexture.
private static SurfaceTexture mSurfaceTexture = null;
private static int[] mTextureNames;
// Only when the video is prepared, we render using SurfaceTexture.
// This in fact is used to avoid showing the obsolete content when
// switching videos.
private static boolean mReadyToUseSurfTex = false;
// For handling the seekTo before prepared, we need to know whether or not
// the video is prepared. Therefore, we differentiate the state between
// prepared and not prepared.
// When the video is not prepared, we will have to save the seekTo time,
// and use it when prepared to play.
private static final int STATE_NOTPREPARED = 0;
private static final int STATE_PREPARED = 1;
// We only need state for handling seekTo
private int mCurrentState;
// Basically for calling back the OnPrepared in the proxy
private HTML5VideoViewProxy mProxy;
// Save the seek time when not prepared. This can happen when switching
// video besides initial load.
private int mSaveSeekTime;
// This is used to find the VideoLayer on the native side.
private int mVideoLayerId;
// Every video will have one MediaPlayer. Given the fact we only have one
// SurfaceTexture, there is only one MediaPlayer in action. Every time we
// switch videos, a new instance of MediaPlayer will be created in reset().
private MediaPlayer mPlayer;
private static HTML5VideoView mInstance = new HTML5VideoView();
// Video control FUNCTIONS:
public void start() {
if (mCurrentState == STATE_PREPARED) {
mPlayer.start();
mReadyToUseSurfTex = true;
}
}
public void pause() {
mPlayer.pause();
}
public int getDuration() {
return mPlayer.getDuration();
}
public int getCurrentPosition() {
return mPlayer.getCurrentPosition();
}
public void seekTo(int pos) {
if (mCurrentState == STATE_PREPARED)
mPlayer.seekTo(pos);
else
mSaveSeekTime = pos;
}
public boolean isPlaying() {
return mPlayer.isPlaying();
}
public void release() {
mPlayer.release();
}
public void stopPlayback() {
mPlayer.stop();
}
private void reset(int videoLayerId) {
mPlayer = new MediaPlayer();
mCurrentState = STATE_NOTPREPARED;
mProxy = null;
mVideoLayerId = videoLayerId;
mReadyToUseSurfTex = false;
}
public static HTML5VideoView getInstance(int videoLayerId) {
// Every time we switch between the videos, a new MediaPlayer will be
// created. Make sure we call the m_player.release() when it is done.
mInstance.reset(videoLayerId);
return mInstance;
}
private HTML5VideoView() {
// This is a singleton across WebViews (i.e. Tabs).
// HTML5VideoViewProxy will reset the internal state every time a new
// video start.
}
public void setMediaController(MediaController m) {
this.setMediaController(m);
}
public void setVideoURI(String uri, Map<String, String> headers) {
// When switching players, surface texture will be reused.
mPlayer.setTexture(getSurfaceTextureInstance());
// When there is exception, we could just bail out silently.
// No Video will be played though. Write the stack for debug
try {
mPlayer.setDataSource(uri, headers);
mPlayer.prepareAsync();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
// TODO [FULL SCREEN SUPPORT]
// Listeners setup FUNCTIONS:
public void setOnCompletionListener(HTML5VideoViewProxy proxy) {
mPlayer.setOnCompletionListener(proxy);
}
public void setOnErrorListener(HTML5VideoViewProxy proxy) {
mPlayer.setOnErrorListener(proxy);
}
public void setOnPreparedListener(HTML5VideoViewProxy proxy) {
mProxy = proxy;
mPlayer.setOnPreparedListener(this);
}
// Inline Video specific FUNCTIONS:
public SurfaceTexture getSurfaceTexture() {
return mSurfaceTexture;
}
public void deleteSurfaceTexture() {
mSurfaceTexture = null;
return;
}
// SurfaceTexture is a singleton here , too
private SurfaceTexture getSurfaceTextureInstance() {
// Create the surface texture.
if (mSurfaceTexture == null)
{
mTextureNames = new int[1];
GLES20.glGenTextures(1, mTextureNames, 0);
mSurfaceTexture = new SurfaceTexture(mTextureNames[0]);
}
return mSurfaceTexture;
}
public int getTextureName() {
return mTextureNames[0];
}
public int getVideoLayerId() {
return mVideoLayerId;
}
public boolean getReadyToUseSurfTex() {
return mReadyToUseSurfTex;
}
public void setFrameAvailableListener(SurfaceTexture.OnFrameAvailableListener l) {
mSurfaceTexture.setOnFrameAvailableListener(l);
}
@Override
public void onPrepared(MediaPlayer mp) {
mCurrentState = STATE_PREPARED;
seekTo(mSaveSeekTime);
if (mProxy != null)
mProxy.onPrepared(mp);
}
// Pause the play and update the play/pause button
public void pauseAndDispatch(HTML5VideoViewProxy proxy) {
if (isPlaying()) {
pause();
if (proxy != null) {
proxy.dispatchOnPaused();
}
}
mReadyToUseSurfTex = false;
}
}
|