Upstream version 9.37.197.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.os.Build;
12 import android.os.ParcelFileDescriptor;
13 import android.text.TextUtils;
14 import android.util.Base64;
15 import android.util.Base64InputStream;
16 import android.util.Log;
17 import android.view.Surface;
18
19 import org.chromium.base.CalledByNative;
20 import org.chromium.base.JNINamespace;
21
22 import java.io.ByteArrayInputStream;
23 import java.io.File;
24 import java.io.FileOutputStream;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.lang.reflect.InvocationTargetException;
28 import java.lang.reflect.Method;
29 import java.util.HashMap;
30
31 /**
32 * A wrapper around android.media.MediaPlayer that allows the native code to use it.
33 * See media/base/android/media_player_bridge.cc for the corresponding native code.
34 */
35 @JNINamespace("media")
36 public class MediaPlayerBridge {
37
38     public static class ResourceLoadingFilter {
39         public boolean shouldOverrideResourceLoading(
40                 MediaPlayer mediaPlayer, Context context, Uri uri) {
41             return false;
42         }
43     }
44
45     private static ResourceLoadingFilter sResourceLoadFilter = null;
46
47     public static void setResourceLoadingFilter(ResourceLoadingFilter filter) {
48         sResourceLoadFilter = filter;
49     }
50
51     private static final String TAG = "MediaPlayerBridge";
52
53     // Local player to forward this to. We don't initialize it here since the subclass might not
54     // want it.
55     private LoadDataUriTask mLoadDataUriTask;
56     private MediaPlayer mPlayer;
57     private long mNativeMediaPlayerBridge;
58
59     @CalledByNative
60     private static MediaPlayerBridge create(long nativeMediaPlayerBridge) {
61         return new MediaPlayerBridge(nativeMediaPlayerBridge);
62     }
63
64     protected MediaPlayerBridge(long nativeMediaPlayerBridge) {
65         mNativeMediaPlayerBridge = nativeMediaPlayerBridge;
66     }
67
68     protected MediaPlayerBridge() {
69     }
70
71     @CalledByNative
72     protected void destroy() {
73         if (mLoadDataUriTask != null) {
74             mLoadDataUriTask.cancel(true);
75             mLoadDataUriTask = null;
76         }
77         mNativeMediaPlayerBridge = 0;
78     }
79
80     protected MediaPlayer getLocalPlayer() {
81         if (mPlayer == null) {
82             mPlayer = new MediaPlayer();
83         }
84         return mPlayer;
85     }
86
87     @CalledByNative
88     protected void setSurface(Surface surface) {
89         getLocalPlayer().setSurface(surface);
90     }
91
92     @CalledByNative
93     protected boolean prepareAsync() {
94         try {
95             getLocalPlayer().prepareAsync();
96         } catch (IllegalStateException e) {
97             Log.e(TAG, "Unable to prepare MediaPlayer.", e);
98             return false;
99         }
100         return true;
101     }
102
103     @CalledByNative
104     protected boolean isPlaying() {
105         return getLocalPlayer().isPlaying();
106     }
107
108     @CalledByNative
109     protected int getVideoWidth() {
110         return getLocalPlayer().getVideoWidth();
111     }
112
113     @CalledByNative
114     protected int getVideoHeight() {
115         return getLocalPlayer().getVideoHeight();
116     }
117
118     @CalledByNative
119     protected int getCurrentPosition() {
120         return getLocalPlayer().getCurrentPosition();
121     }
122
123     @CalledByNative
124     protected int getDuration() {
125         return getLocalPlayer().getDuration();
126     }
127
128     @CalledByNative
129     protected void release() {
130         getLocalPlayer().release();
131     }
132
133     @CalledByNative
134     protected void setVolume(double volume) {
135         getLocalPlayer().setVolume((float) volume, (float) volume);
136     }
137
138     @CalledByNative
139     protected void start() {
140         getLocalPlayer().start();
141     }
142
143     @CalledByNative
144     protected void pause() {
145         getLocalPlayer().pause();
146     }
147
148     @CalledByNative
149     protected void seekTo(int msec) throws IllegalStateException {
150         getLocalPlayer().seekTo(msec);
151     }
152
153     @CalledByNative
154     protected boolean setDataSource(
155             Context context, String url, String cookies, String userAgent, boolean hideUrlLog) {
156         Uri uri = Uri.parse(url);
157         HashMap<String, String> headersMap = new HashMap<String, String>();
158         if (hideUrlLog) headersMap.put("x-hide-urls-from-log", "true");
159         if (!TextUtils.isEmpty(cookies)) headersMap.put("Cookie", cookies);
160         if (!TextUtils.isEmpty(userAgent)) headersMap.put("User-Agent", userAgent);
161         // The security origin check is enforced for devices above K. For devices below K,
162         // only anonymous media HTTP request (no cookies) may be considered same-origin.
163         // Note that if the server rejects the request we must not consider it same-origin.
164         if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
165             headersMap.put("allow-cross-domain-redirect", "false");
166         }
167         try {
168             if (sResourceLoadFilter != null &&
169                     sResourceLoadFilter.shouldOverrideResourceLoading(
170                             getLocalPlayer(), context, uri)) {
171                 return true;
172             }
173             getLocalPlayer().setDataSource(context, uri, headersMap);
174             return true;
175         } catch (Exception e) {
176             return false;
177         }
178     }
179
180     @CalledByNative
181     protected boolean setDataSourceFromFd(int fd, long offset, long length) {
182         try {
183             ParcelFileDescriptor parcelFd = ParcelFileDescriptor.adoptFd(fd);
184             getLocalPlayer().setDataSource(parcelFd.getFileDescriptor(), offset, length);
185             parcelFd.close();
186             return true;
187         } catch (IOException e) {
188             Log.e(TAG, "Failed to set data source from file descriptor: " + e);
189             return false;
190         }
191     }
192
193     @CalledByNative
194     protected boolean setDataUriDataSource(final Context context, final String url) {
195         if (mLoadDataUriTask != null) {
196             mLoadDataUriTask.cancel(true);
197             mLoadDataUriTask = null;
198         }
199
200         if (!url.startsWith("data:")) return false;
201         int headerStop = url.indexOf(',');
202         if (headerStop == -1) return false;
203         String header = url.substring(0, headerStop);
204         final String data = url.substring(headerStop + 1);
205
206         String headerContent = header.substring(5);
207         String headerInfo[] = headerContent.split(";");
208         if (headerInfo.length != 2) return false;
209         if (!"base64".equals(headerInfo[1])) return false;
210
211         mLoadDataUriTask = new LoadDataUriTask(context, data);
212         mLoadDataUriTask.execute();
213         return true;
214     }
215
216     private class LoadDataUriTask extends AsyncTask <Void, Void, Boolean> {
217         private final String mData;
218         private final Context mContext;
219         private File mTempFile;
220
221         public LoadDataUriTask(Context context, String data) {
222             mData = data;
223             mContext = context;
224         }
225
226         @Override
227         protected Boolean doInBackground(Void... params) {
228             FileOutputStream fos = null;
229             try {
230                 mTempFile = File.createTempFile("decoded", "mediadata");
231                 fos = new FileOutputStream(mTempFile);
232                 InputStream stream = new ByteArrayInputStream(mData.getBytes());
233                 Base64InputStream decoder = new Base64InputStream(stream, Base64.DEFAULT);
234                 byte[] buffer = new byte[1024];
235                 int len;
236                 while ((len = decoder.read(buffer)) != -1) {
237                     fos.write(buffer, 0, len);
238                 }
239                 decoder.close();
240                 return true;
241             } catch (IOException e) {
242                 return false;
243             } finally {
244                 try {
245                     if (fos != null) fos.close();
246                 } catch (IOException e) {
247                     // Can't do anything.
248                 }
249             }
250         }
251
252         @Override
253         protected void onPostExecute(Boolean result) {
254             if (isCancelled()) {
255                 deleteFile();
256                 return;
257             }
258
259             try {
260                 getLocalPlayer().setDataSource(mContext, Uri.fromFile(mTempFile));
261             } catch (IOException e) {
262                 result = false;
263             }
264
265             deleteFile();
266             assert (mNativeMediaPlayerBridge != 0);
267             nativeOnDidSetDataUriDataSource(mNativeMediaPlayerBridge, result);
268         }
269
270         private void deleteFile() {
271             if (mTempFile == null) return;
272             if (!mTempFile.delete()) {
273                 // File will be deleted when MediaPlayer releases its handler.
274                 Log.e(TAG, "Failed to delete temporary file: " + mTempFile);
275                 assert (false);
276             }
277         }
278     }
279
280     protected void setOnBufferingUpdateListener(MediaPlayer.OnBufferingUpdateListener listener) {
281         getLocalPlayer().setOnBufferingUpdateListener(listener);
282     }
283
284     protected void setOnCompletionListener(MediaPlayer.OnCompletionListener listener) {
285         getLocalPlayer().setOnCompletionListener(listener);
286     }
287
288     protected void setOnErrorListener(MediaPlayer.OnErrorListener listener) {
289         getLocalPlayer().setOnErrorListener(listener);
290     }
291
292     protected void setOnPreparedListener(MediaPlayer.OnPreparedListener listener) {
293         getLocalPlayer().setOnPreparedListener(listener);
294     }
295
296     protected void setOnSeekCompleteListener(MediaPlayer.OnSeekCompleteListener listener) {
297         getLocalPlayer().setOnSeekCompleteListener(listener);
298     }
299
300     protected void setOnVideoSizeChangedListener(MediaPlayer.OnVideoSizeChangedListener listener) {
301         getLocalPlayer().setOnVideoSizeChangedListener(listener);
302     }
303
304     protected static class AllowedOperations {
305         private final boolean mCanPause;
306         private final boolean mCanSeekForward;
307         private final boolean mCanSeekBackward;
308
309         public AllowedOperations(boolean canPause, boolean canSeekForward,
310                 boolean canSeekBackward) {
311             mCanPause = canPause;
312             mCanSeekForward = canSeekForward;
313             mCanSeekBackward = canSeekBackward;
314         }
315
316         @CalledByNative("AllowedOperations")
317         private boolean canPause() { return mCanPause; }
318
319         @CalledByNative("AllowedOperations")
320         private boolean canSeekForward() { return mCanSeekForward; }
321
322         @CalledByNative("AllowedOperations")
323         private boolean canSeekBackward() { return mCanSeekBackward; }
324     }
325
326     /**
327      * Returns an AllowedOperations object to show all the operations that are
328      * allowed on the media player.
329      */
330     @CalledByNative
331     protected AllowedOperations getAllowedOperations() {
332         MediaPlayer player = getLocalPlayer();
333         boolean canPause = true;
334         boolean canSeekForward = true;
335         boolean canSeekBackward = true;
336         try {
337             Method getMetadata = player.getClass().getDeclaredMethod(
338                     "getMetadata", boolean.class, boolean.class);
339             getMetadata.setAccessible(true);
340             Object data = getMetadata.invoke(player, false, false);
341             if (data != null) {
342                 Class<?> metadataClass = data.getClass();
343                 Method hasMethod = metadataClass.getDeclaredMethod("has", int.class);
344                 Method getBooleanMethod = metadataClass.getDeclaredMethod("getBoolean", int.class);
345
346                 int pause = (Integer) metadataClass.getField("PAUSE_AVAILABLE").get(null);
347                 int seekForward =
348                     (Integer) metadataClass.getField("SEEK_FORWARD_AVAILABLE").get(null);
349                 int seekBackward =
350                         (Integer) metadataClass.getField("SEEK_BACKWARD_AVAILABLE").get(null);
351                 hasMethod.setAccessible(true);
352                 getBooleanMethod.setAccessible(true);
353                 canPause = !((Boolean) hasMethod.invoke(data, pause))
354                         || ((Boolean) getBooleanMethod.invoke(data, pause));
355                 canSeekForward = !((Boolean) hasMethod.invoke(data, seekForward))
356                         || ((Boolean) getBooleanMethod.invoke(data, seekForward));
357                 canSeekBackward = !((Boolean) hasMethod.invoke(data, seekBackward))
358                         || ((Boolean) getBooleanMethod.invoke(data, seekBackward));
359             }
360         } catch (NoSuchMethodException e) {
361             Log.e(TAG, "Cannot find getMetadata() method: " + e);
362         } catch (InvocationTargetException e) {
363             Log.e(TAG, "Cannot invoke MediaPlayer.getMetadata() method: " + e);
364         } catch (IllegalAccessException e) {
365             Log.e(TAG, "Cannot access metadata: " + e);
366         } catch (NoSuchFieldException e) {
367             Log.e(TAG, "Cannot find matching fields in Metadata class: " + e);
368         }
369         return new AllowedOperations(canPause, canSeekForward, canSeekBackward);
370     }
371
372     private native void nativeOnDidSetDataUriDataSource(long nativeMediaPlayerBridge,
373                                                         boolean success);
374 }