1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 package org.chromium.media;
7 import android.content.Context;
8 import android.media.MediaPlayer;
9 import android.net.Uri;
10 import android.os.AsyncTask;
11 import android.text.TextUtils;
12 import android.util.Base64;
13 import android.util.Base64InputStream;
14 import android.util.Log;
15 import android.view.Surface;
17 import org.chromium.base.CalledByNative;
18 import org.chromium.base.JNINamespace;
20 import java.io.ByteArrayInputStream;
22 import java.io.FileOutputStream;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.lang.reflect.InvocationTargetException;
26 import java.lang.reflect.Method;
27 import java.util.HashMap;
30 * A wrapper around android.media.MediaPlayer that allows the native code to use it.
31 * See media/base/android/media_player_bridge.cc for the corresponding native code.
33 @JNINamespace("media")
34 public class MediaPlayerBridge {
36 public static class ResourceLoadingFilter {
37 public boolean shouldOverrideResourceLoading(
38 MediaPlayer mediaPlayer, Context context, Uri uri) {
43 private static ResourceLoadingFilter sResourceLoadFilter = null;
45 public static void setResourceLoadingFilter(ResourceLoadingFilter filter) {
46 sResourceLoadFilter = filter;
49 private static final String TAG = "MediaPlayerBridge";
51 // Local player to forward this to. We don't initialize it here since the subclass might not
53 private LoadDataUriTask mLoadDataUriTask;
54 private MediaPlayer mPlayer;
55 private long mNativeMediaPlayerBridge;
58 private static MediaPlayerBridge create(long nativeMediaPlayerBridge) {
59 return new MediaPlayerBridge(nativeMediaPlayerBridge);
62 protected MediaPlayerBridge(long nativeMediaPlayerBridge) {
63 mNativeMediaPlayerBridge = nativeMediaPlayerBridge;
66 protected MediaPlayerBridge() {
70 protected void destroy() {
71 if (mLoadDataUriTask != null) {
72 mLoadDataUriTask.cancel(true);
73 mLoadDataUriTask = null;
75 mNativeMediaPlayerBridge = 0;
78 protected MediaPlayer getLocalPlayer() {
79 if (mPlayer == null) {
80 mPlayer = new MediaPlayer();
86 protected void setSurface(Surface surface) {
87 getLocalPlayer().setSurface(surface);
91 protected boolean prepareAsync() {
93 getLocalPlayer().prepareAsync();
94 } catch (IllegalStateException e) {
95 Log.e(TAG, "Unable to prepare MediaPlayer.", e);
102 protected boolean isPlaying() {
103 return getLocalPlayer().isPlaying();
107 protected int getVideoWidth() {
108 return getLocalPlayer().getVideoWidth();
112 protected int getVideoHeight() {
113 return getLocalPlayer().getVideoHeight();
117 protected int getCurrentPosition() {
118 return getLocalPlayer().getCurrentPosition();
122 protected int getDuration() {
123 return getLocalPlayer().getDuration();
127 protected void release() {
128 getLocalPlayer().release();
132 protected void setVolume(double volume) {
133 getLocalPlayer().setVolume((float) volume, (float) volume);
137 protected void start() {
138 getLocalPlayer().start();
142 protected void pause() {
143 getLocalPlayer().pause();
147 protected void seekTo(int msec) throws IllegalStateException {
148 getLocalPlayer().seekTo(msec);
152 protected boolean setDataSource(
153 Context context, String url, String cookies, String userAgent, boolean hideUrlLog) {
154 Uri uri = Uri.parse(url);
155 HashMap<String, String> headersMap = new HashMap<String, String>();
156 if (hideUrlLog) headersMap.put("x-hide-urls-from-log", "true");
157 if (!TextUtils.isEmpty(cookies)) headersMap.put("Cookie", cookies);
158 if (!TextUtils.isEmpty(userAgent)) headersMap.put("User-Agent", userAgent);
160 if (sResourceLoadFilter != null &&
161 sResourceLoadFilter.shouldOverrideResourceLoading(
162 getLocalPlayer(), context, uri)) {
165 getLocalPlayer().setDataSource(context, uri, headersMap);
167 } catch (Exception e) {
173 protected boolean setDataUriDataSource(final Context context, final String url) {
174 if (mLoadDataUriTask != null) {
175 mLoadDataUriTask.cancel(true);
176 mLoadDataUriTask = null;
179 if (!url.startsWith("data:")) return false;
180 int headerStop = url.indexOf(',');
181 if (headerStop == -1) return false;
182 String header = url.substring(0, headerStop);
183 final String data = url.substring(headerStop + 1);
185 String headerContent = header.substring(5);
186 String headerInfo[] = headerContent.split(";");
187 if (headerInfo.length != 2) return false;
188 if (!"base64".equals(headerInfo[1])) return false;
190 mLoadDataUriTask = new LoadDataUriTask(context, data);
191 mLoadDataUriTask.execute();
195 private class LoadDataUriTask extends AsyncTask <Void, Void, Boolean> {
196 private final String mData;
197 private final Context mContext;
198 private File mTempFile;
200 public LoadDataUriTask(Context context, String data) {
206 protected Boolean doInBackground(Void... params) {
207 FileOutputStream fos = null;
209 mTempFile = File.createTempFile("decoded", "mediadata");
210 fos = new FileOutputStream(mTempFile);
211 InputStream stream = new ByteArrayInputStream(mData.getBytes());
212 Base64InputStream decoder = new Base64InputStream(stream, Base64.DEFAULT);
213 byte[] buffer = new byte[1024];
215 while ((len = decoder.read(buffer)) != -1) {
216 fos.write(buffer, 0, len);
220 } catch (IOException e) {
224 if (fos != null) fos.close();
225 } catch (IOException e) {
226 // Can't do anything.
232 protected void onPostExecute(Boolean result) {
239 getLocalPlayer().setDataSource(mContext, Uri.fromFile(mTempFile));
240 } catch (IOException e) {
245 assert (mNativeMediaPlayerBridge != 0);
246 nativeOnDidSetDataUriDataSource(mNativeMediaPlayerBridge, result);
249 private void deleteFile() {
250 if (mTempFile == null) return;
251 if (!mTempFile.delete()) {
252 // File will be deleted when MediaPlayer releases its handler.
253 Log.e(TAG, "Failed to delete temporary file: " + mTempFile);
259 protected void setOnBufferingUpdateListener(MediaPlayer.OnBufferingUpdateListener listener) {
260 getLocalPlayer().setOnBufferingUpdateListener(listener);
263 protected void setOnCompletionListener(MediaPlayer.OnCompletionListener listener) {
264 getLocalPlayer().setOnCompletionListener(listener);
267 protected void setOnErrorListener(MediaPlayer.OnErrorListener listener) {
268 getLocalPlayer().setOnErrorListener(listener);
271 protected void setOnPreparedListener(MediaPlayer.OnPreparedListener listener) {
272 getLocalPlayer().setOnPreparedListener(listener);
275 protected void setOnSeekCompleteListener(MediaPlayer.OnSeekCompleteListener listener) {
276 getLocalPlayer().setOnSeekCompleteListener(listener);
279 protected void setOnVideoSizeChangedListener(MediaPlayer.OnVideoSizeChangedListener listener) {
280 getLocalPlayer().setOnVideoSizeChangedListener(listener);
283 protected static class AllowedOperations {
284 private final boolean mCanPause;
285 private final boolean mCanSeekForward;
286 private final boolean mCanSeekBackward;
288 public AllowedOperations(boolean canPause, boolean canSeekForward,
289 boolean canSeekBackward) {
290 mCanPause = canPause;
291 mCanSeekForward = canSeekForward;
292 mCanSeekBackward = canSeekBackward;
295 @CalledByNative("AllowedOperations")
296 private boolean canPause() { return mCanPause; }
298 @CalledByNative("AllowedOperations")
299 private boolean canSeekForward() { return mCanSeekForward; }
301 @CalledByNative("AllowedOperations")
302 private boolean canSeekBackward() { return mCanSeekBackward; }
306 * Returns an AllowedOperations object to show all the operations that are
307 * allowed on the media player.
310 protected AllowedOperations getAllowedOperations() {
311 MediaPlayer player = getLocalPlayer();
312 boolean canPause = true;
313 boolean canSeekForward = true;
314 boolean canSeekBackward = true;
316 Method getMetadata = player.getClass().getDeclaredMethod(
317 "getMetadata", boolean.class, boolean.class);
318 getMetadata.setAccessible(true);
319 Object data = getMetadata.invoke(player, false, false);
321 Class<?> metadataClass = data.getClass();
322 Method hasMethod = metadataClass.getDeclaredMethod("has", int.class);
323 Method getBooleanMethod = metadataClass.getDeclaredMethod("getBoolean", int.class);
325 int pause = (Integer) metadataClass.getField("PAUSE_AVAILABLE").get(null);
327 (Integer) metadataClass.getField("SEEK_FORWARD_AVAILABLE").get(null);
329 (Integer) metadataClass.getField("SEEK_BACKWARD_AVAILABLE").get(null);
330 hasMethod.setAccessible(true);
331 getBooleanMethod.setAccessible(true);
332 canPause = !((Boolean) hasMethod.invoke(data, pause))
333 || ((Boolean) getBooleanMethod.invoke(data, pause));
334 canSeekForward = !((Boolean) hasMethod.invoke(data, seekForward))
335 || ((Boolean) getBooleanMethod.invoke(data, seekForward));
336 canSeekBackward = !((Boolean) hasMethod.invoke(data, seekBackward))
337 || ((Boolean) getBooleanMethod.invoke(data, seekBackward));
339 } catch (NoSuchMethodException e) {
340 Log.e(TAG, "Cannot find getMetadata() method: " + e);
341 } catch (InvocationTargetException e) {
342 Log.e(TAG, "Cannot invoke MediaPlayer.getMetadata() method: " + e);
343 } catch (IllegalAccessException e) {
344 Log.e(TAG, "Cannot access metadata: " + e);
345 } catch (NoSuchFieldException e) {
346 Log.e(TAG, "Cannot find matching fields in Metadata class: " + e);
348 return new AllowedOperations(canPause, canSeekForward, canSeekBackward);
351 private native void nativeOnDidSetDataUriDataSource(long nativeMediaPlayerBridge,