Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / media / base / android / java / src / org / chromium / media / MediaPlayerBridge.java
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.
4
5 package org.chromium.media;
6
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;
16
17 import org.chromium.base.CalledByNative;
18 import org.chromium.base.JNINamespace;
19
20 import java.io.ByteArrayInputStream;
21 import java.io.File;
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;
28
29 /**
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.
32 */
33 @JNINamespace("media")
34 public class MediaPlayerBridge {
35
36     public static class ResourceLoadingFilter {
37         public boolean shouldOverrideResourceLoading(
38                 MediaPlayer mediaPlayer, Context context, Uri uri) {
39             return false;
40         }
41     }
42
43     private static ResourceLoadingFilter sResourceLoadFilter = null;
44
45     public static void setResourceLoadingFilter(ResourceLoadingFilter filter) {
46         sResourceLoadFilter = filter;
47     }
48
49     private static final String TAG = "MediaPlayerBridge";
50
51     // Local player to forward this to. We don't initialize it here since the subclass might not
52     // want it.
53     private LoadDataUriTask mLoadDataUriTask;
54     private MediaPlayer mPlayer;
55     private long mNativeMediaPlayerBridge;
56
57     @CalledByNative
58     private static MediaPlayerBridge create(long nativeMediaPlayerBridge) {
59         return new MediaPlayerBridge(nativeMediaPlayerBridge);
60     }
61
62     protected MediaPlayerBridge(long nativeMediaPlayerBridge) {
63         mNativeMediaPlayerBridge = nativeMediaPlayerBridge;
64     }
65
66     protected MediaPlayerBridge() {
67     }
68
69     @CalledByNative
70     protected void destroy() {
71         if (mLoadDataUriTask != null) {
72             mLoadDataUriTask.cancel(true);
73             mLoadDataUriTask = null;
74         }
75         mNativeMediaPlayerBridge = 0;
76     }
77
78     protected MediaPlayer getLocalPlayer() {
79         if (mPlayer == null) {
80             mPlayer = new MediaPlayer();
81         }
82         return mPlayer;
83     }
84
85     @CalledByNative
86     protected void setSurface(Surface surface) {
87         getLocalPlayer().setSurface(surface);
88     }
89
90     @CalledByNative
91     protected boolean prepareAsync() {
92         try {
93             getLocalPlayer().prepareAsync();
94         } catch (IllegalStateException e) {
95             Log.e(TAG, "Unable to prepare MediaPlayer.", e);
96             return false;
97         }
98         return true;
99     }
100
101     @CalledByNative
102     protected boolean isPlaying() {
103         return getLocalPlayer().isPlaying();
104     }
105
106     @CalledByNative
107     protected int getVideoWidth() {
108         return getLocalPlayer().getVideoWidth();
109     }
110
111     @CalledByNative
112     protected int getVideoHeight() {
113         return getLocalPlayer().getVideoHeight();
114     }
115
116     @CalledByNative
117     protected int getCurrentPosition() {
118         return getLocalPlayer().getCurrentPosition();
119     }
120
121     @CalledByNative
122     protected int getDuration() {
123         return getLocalPlayer().getDuration();
124     }
125
126     @CalledByNative
127     protected void release() {
128         getLocalPlayer().release();
129     }
130
131     @CalledByNative
132     protected void setVolume(double volume) {
133         getLocalPlayer().setVolume((float) volume, (float) volume);
134     }
135
136     @CalledByNative
137     protected void start() {
138         getLocalPlayer().start();
139     }
140
141     @CalledByNative
142     protected void pause() {
143         getLocalPlayer().pause();
144     }
145
146     @CalledByNative
147     protected void seekTo(int msec) throws IllegalStateException {
148         getLocalPlayer().seekTo(msec);
149     }
150
151     @CalledByNative
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);
159         try {
160             if (sResourceLoadFilter != null &&
161                     sResourceLoadFilter.shouldOverrideResourceLoading(
162                             getLocalPlayer(), context, uri)) {
163                 return true;
164             }
165             getLocalPlayer().setDataSource(context, uri, headersMap);
166             return true;
167         } catch (Exception e) {
168             return false;
169         }
170     }
171
172     @CalledByNative
173     protected boolean setDataUriDataSource(final Context context, final String url) {
174         if (mLoadDataUriTask != null) {
175             mLoadDataUriTask.cancel(true);
176             mLoadDataUriTask = null;
177         }
178
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);
184
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;
189
190         mLoadDataUriTask = new LoadDataUriTask(context, data);
191         mLoadDataUriTask.execute();
192         return true;
193     }
194
195     private class LoadDataUriTask extends AsyncTask <Void, Void, Boolean> {
196         private final String mData;
197         private final Context mContext;
198         private File mTempFile;
199
200         public LoadDataUriTask(Context context, String data) {
201             mData = data;
202             mContext = context;
203         }
204
205         @Override
206         protected Boolean doInBackground(Void... params) {
207             FileOutputStream fos = null;
208             try {
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];
214                 int len;
215                 while ((len = decoder.read(buffer)) != -1) {
216                     fos.write(buffer, 0, len);
217                 }
218                 decoder.close();
219                 return true;
220             } catch (IOException e) {
221                 return false;
222             } finally {
223                 try {
224                     if (fos != null) fos.close();
225                 } catch (IOException e) {
226                     // Can't do anything.
227                 }
228             }
229         }
230
231         @Override
232         protected void onPostExecute(Boolean result) {
233             if (isCancelled()) {
234                 deleteFile();
235                 return;
236             }
237
238             try {
239                 getLocalPlayer().setDataSource(mContext, Uri.fromFile(mTempFile));
240             } catch (IOException e) {
241                 result = false;
242             }
243
244             deleteFile();
245             assert (mNativeMediaPlayerBridge != 0);
246             nativeOnDidSetDataUriDataSource(mNativeMediaPlayerBridge, result);
247         }
248
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);
254                 assert (false);
255             }
256         }
257     }
258
259     protected void setOnBufferingUpdateListener(MediaPlayer.OnBufferingUpdateListener listener) {
260         getLocalPlayer().setOnBufferingUpdateListener(listener);
261     }
262
263     protected void setOnCompletionListener(MediaPlayer.OnCompletionListener listener) {
264         getLocalPlayer().setOnCompletionListener(listener);
265     }
266
267     protected void setOnErrorListener(MediaPlayer.OnErrorListener listener) {
268         getLocalPlayer().setOnErrorListener(listener);
269     }
270
271     protected void setOnPreparedListener(MediaPlayer.OnPreparedListener listener) {
272         getLocalPlayer().setOnPreparedListener(listener);
273     }
274
275     protected void setOnSeekCompleteListener(MediaPlayer.OnSeekCompleteListener listener) {
276         getLocalPlayer().setOnSeekCompleteListener(listener);
277     }
278
279     protected void setOnVideoSizeChangedListener(MediaPlayer.OnVideoSizeChangedListener listener) {
280         getLocalPlayer().setOnVideoSizeChangedListener(listener);
281     }
282
283     protected static class AllowedOperations {
284         private final boolean mCanPause;
285         private final boolean mCanSeekForward;
286         private final boolean mCanSeekBackward;
287
288         public AllowedOperations(boolean canPause, boolean canSeekForward,
289                 boolean canSeekBackward) {
290             mCanPause = canPause;
291             mCanSeekForward = canSeekForward;
292             mCanSeekBackward = canSeekBackward;
293         }
294
295         @CalledByNative("AllowedOperations")
296         private boolean canPause() { return mCanPause; }
297
298         @CalledByNative("AllowedOperations")
299         private boolean canSeekForward() { return mCanSeekForward; }
300
301         @CalledByNative("AllowedOperations")
302         private boolean canSeekBackward() { return mCanSeekBackward; }
303     }
304
305     /**
306      * Returns an AllowedOperations object to show all the operations that are
307      * allowed on the media player.
308      */
309     @CalledByNative
310     protected AllowedOperations getAllowedOperations() {
311         MediaPlayer player = getLocalPlayer();
312         boolean canPause = true;
313         boolean canSeekForward = true;
314         boolean canSeekBackward = true;
315         try {
316             Method getMetadata = player.getClass().getDeclaredMethod(
317                     "getMetadata", boolean.class, boolean.class);
318             getMetadata.setAccessible(true);
319             Object data = getMetadata.invoke(player, false, false);
320             if (data != null) {
321                 Class<?> metadataClass = data.getClass();
322                 Method hasMethod = metadataClass.getDeclaredMethod("has", int.class);
323                 Method getBooleanMethod = metadataClass.getDeclaredMethod("getBoolean", int.class);
324
325                 int pause = (Integer) metadataClass.getField("PAUSE_AVAILABLE").get(null);
326                 int seekForward =
327                     (Integer) metadataClass.getField("SEEK_FORWARD_AVAILABLE").get(null);
328                 int seekBackward =
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));
338             }
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);
347         }
348         return new AllowedOperations(canPause, canSeekForward, canSeekBackward);
349     }
350
351     private native void nativeOnDidSetDataUriDataSource(long nativeMediaPlayerBridge,
352                                                         boolean success);
353 }