summaryrefslogtreecommitdiff
path: root/playback/player/android/app/src
diff options
context:
space:
mode:
Diffstat (limited to 'playback/player/android/app/src')
-rw-r--r--playback/player/android/app/src/main/AndroidManifest.xml344
-rw-r--r--playback/player/android/app/src/main/java/org/freedesktop/gstreamer/Player.java241
-rw-r--r--playback/player/android/app/src/main/java/org/freedesktop/gstreamer/player/GStreamerSurfaceView.java110
-rw-r--r--playback/player/android/app/src/main/java/org/freedesktop/gstreamer/player/Play.java196
-rw-r--r--playback/player/android/app/src/main/jni/Android.mk39
-rw-r--r--playback/player/android/app/src/main/jni/Application.mk1
-rw-r--r--playback/player/android/app/src/main/jni/player.c497
-rw-r--r--playback/player/android/app/src/main/res/layout/main.xml62
-rw-r--r--playback/player/android/app/src/main/res/values/strings.xml6
9 files changed, 1496 insertions, 0 deletions
diff --git a/playback/player/android/app/src/main/AndroidManifest.xml b/playback/player/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..9823f22
--- /dev/null
+++ b/playback/player/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,344 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="org.freedesktop.gstreamer.play">
+
+ <uses-permission android:name="android.permission.INTERNET"/>
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+ <uses-permission android:name="android.permission.WAKE_LOCK"/>
+ <uses-feature android:glEsVersion="0x00020000"/>
+
+ <application android:label="@string/app_name">
+ <activity android:name=".Play"
+ android:label="@string/app_name">
+ <!-- Files whose MIME type is known to Android -->
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+
+ <data android:mimeType="audio/*" />
+ <data android:mimeType="video/*" />
+ <data android:mimeType="image/*" />
+ <data android:mimeType="*/rmvb" />
+ <data android:mimeType="*/avi" />
+ <data android:mimeType="*/mkv" />
+ <data android:mimeType="application/3gpp*" />
+ <data android:mimeType="application/mp4" />
+ <data android:mimeType="application/mpeg*" />
+ <data android:mimeType="application/ogg" />
+ <data android:mimeType="application/sdp" />
+ <data android:mimeType="application/vnd.3gp*" />
+ <data android:mimeType="application/vnd.apple.mpegurl" />
+ <data android:mimeType="application/vnd.rn-realmedia*" />
+ <data android:mimeType="application/x-iso9660-image" />
+ <data android:mimeType="application/x-extension-mp4" />
+ <data android:mimeType="application/x-flac" />
+ <data android:mimeType="application/x-matroska" />
+ <data android:mimeType="application/x-mpegURL" />
+ <data android:mimeType="application/x-ogg" />
+ <data android:mimeType="application/x-quicktimeplayer" />
+ </intent-filter>
+
+ <!-- Files with unknown MIME type.
+ The list of extensions and supported protocols can certainly be extended. -->
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+
+ <data android:scheme="file" />
+ <data android:scheme="http" />
+ <data android:scheme="https" />
+
+ <!-- video -->
+ <data android:pathPattern=".*\\.3g2" />
+ <data android:pathPattern=".*\\.3gp" />
+ <data android:pathPattern=".*\\.3gp2" />
+ <data android:pathPattern=".*\\.3gpp" />
+ <data android:pathPattern=".*\\.amv" />
+ <data android:pathPattern=".*\\.asf" />
+ <data android:pathPattern=".*\\.avi" />
+ <data android:pathPattern=".*\\.divx" />
+ <data android:pathPattern=".*\\.drc" />
+ <data android:pathPattern=".*\\.dv" />
+ <data android:pathPattern=".*\\.f4v" />
+ <data android:pathPattern=".*\\.flv" />
+ <data android:pathPattern=".*\\.gvi" />
+ <data android:pathPattern=".*\\.gxf" />
+ <data android:pathPattern=".*\\.ismv" />
+ <data android:pathPattern=".*\\.iso" />
+ <data android:pathPattern=".*\\.m1v" />
+ <data android:pathPattern=".*\\.m2v" />
+ <data android:pathPattern=".*\\.m2t" />
+ <data android:pathPattern=".*\\.m2ts" />
+ <!-- <data android:pathPattern=".*\\.m3u" /> -->
+ <data android:pathPattern=".*\\.m3u8" />
+ <data android:pathPattern=".*\\.mkv" />
+ <data android:pathPattern=".*\\.mov" />
+ <data android:pathPattern=".*\\.mp2" />
+ <data android:pathPattern=".*\\.mp2v" />
+ <data android:pathPattern=".*\\.mp4" />
+ <data android:pathPattern=".*\\.mp4v" />
+ <data android:pathPattern=".*\\.mpe" />
+ <data android:pathPattern=".*\\.mpeg" />
+ <data android:pathPattern=".*\\.mpeg1" />
+ <data android:pathPattern=".*\\.mpeg2" />
+ <data android:pathPattern=".*\\.mpeg4" />
+ <data android:pathPattern=".*\\.mpg" />
+ <data android:pathPattern=".*\\.mpv2" />
+ <data android:pathPattern=".*\\.mts" />
+ <data android:pathPattern=".*\\.mtv" />
+ <data android:pathPattern=".*\\.mxf" />
+ <data android:pathPattern=".*\\.mxg" />
+ <data android:pathPattern=".*\\.nsv" />
+ <data android:pathPattern=".*\\.nut" />
+ <data android:pathPattern=".*\\.nuv" />
+ <data android:pathPattern=".*\\.ogm" />
+ <data android:pathPattern=".*\\.ogv" />
+ <data android:pathPattern=".*\\.ogx" />
+ <data android:pathPattern=".*\\.ps" />
+ <data android:pathPattern=".*\\.rec" />
+ <data android:pathPattern=".*\\.rm" />
+ <data android:pathPattern=".*\\.rmvb" />
+ <data android:pathPattern=".*\\.tod" />
+ <data android:pathPattern=".*\\.ts" />
+ <data android:pathPattern=".*\\.tts" />
+ <data android:pathPattern=".*\\.vob" />
+ <data android:pathPattern=".*\\.vro" />
+ <data android:pathPattern=".*\\.webm" />
+ <data android:pathPattern=".*\\.wm" />
+ <data android:pathPattern=".*\\.wmv" />
+ <data android:pathPattern=".*\\.wtv" />
+ <data android:pathPattern=".*\\.xesc" />
+ <data android:pathPattern=".*\\.3G2" />
+ <data android:pathPattern=".*\\.3GP" />
+ <data android:pathPattern=".*\\.3GP2" />
+ <data android:pathPattern=".*\\.3GPP" />
+ <data android:pathPattern=".*\\.AMV" />
+ <data android:pathPattern=".*\\.ASF" />
+ <data android:pathPattern=".*\\.AVI" />
+ <data android:pathPattern=".*\\.DIVX" />
+ <data android:pathPattern=".*\\.DRC" />
+ <data android:pathPattern=".*\\.DV" />
+ <data android:pathPattern=".*\\.F4V" />
+ <data android:pathPattern=".*\\.FLV" />
+ <data android:pathPattern=".*\\.GVI" />
+ <data android:pathPattern=".*\\.GXF" />
+ <data android:pathPattern=".*\\.ISMV" />
+ <data android:pathPattern=".*\\.ISO" />
+ <data android:pathPattern=".*\\.M1V" />
+ <data android:pathPattern=".*\\.M2V" />
+ <data android:pathPattern=".*\\.M2T" />
+ <data android:pathPattern=".*\\.M2TS" />
+ <!-- <data android:pathPattern=".*\\.M3U" /> -->
+ <data android:pathPattern=".*\\.M3U8" />
+ <data android:pathPattern=".*\\.MKV" />
+ <data android:pathPattern=".*\\.MOV" />
+ <data android:pathPattern=".*\\.MP2" />
+ <data android:pathPattern=".*\\.MP2V" />
+ <data android:pathPattern=".*\\.MP4" />
+ <data android:pathPattern=".*\\.MP4V" />
+ <data android:pathPattern=".*\\.MPE" />
+ <data android:pathPattern=".*\\.MPEG" />
+ <data android:pathPattern=".*\\.MPEG1" />
+ <data android:pathPattern=".*\\.MPEG2" />
+ <data android:pathPattern=".*\\.MPEG4" />
+ <data android:pathPattern=".*\\.MPG" />
+ <data android:pathPattern=".*\\.MPV2" />
+ <data android:pathPattern=".*\\.MTS" />
+ <data android:pathPattern=".*\\.MTV" />
+ <data android:pathPattern=".*\\.MXF" />
+ <data android:pathPattern=".*\\.MXG" />
+ <data android:pathPattern=".*\\.NSV" />
+ <data android:pathPattern=".*\\.NUT" />
+ <data android:pathPattern=".*\\.NUV" />
+ <data android:pathPattern=".*\\.OGM" />
+ <data android:pathPattern=".*\\.OGV" />
+ <data android:pathPattern=".*\\.OGX" />
+ <data android:pathPattern=".*\\.PS" />
+ <data android:pathPattern=".*\\.REC" />
+ <data android:pathPattern=".*\\.RM" />
+ <data android:pathPattern=".*\\.RMVB" />
+ <data android:pathPattern=".*\\.TOD" />
+ <data android:pathPattern=".*\\.TS" />
+ <data android:pathPattern=".*\\.TTS" />
+ <data android:pathPattern=".*\\.VOB" />
+ <data android:pathPattern=".*\\.VRO" />
+ <data android:pathPattern=".*\\.WEBM" />
+ <data android:pathPattern=".*\\.WM" />
+ <data android:pathPattern=".*\\.WMV" />
+ <data android:pathPattern=".*\\.WTV" />
+ <data android:pathPattern=".*\\.XESC" />
+
+ <!-- audio -->
+ <data android:pathPattern=".*\\.3ga" />
+ <data android:pathPattern=".*\\.a52" />
+ <data android:pathPattern=".*\\.aac" />
+ <data android:pathPattern=".*\\.ac3" />
+ <data android:pathPattern=".*\\.adt" />
+ <data android:pathPattern=".*\\.adts" />
+ <data android:pathPattern=".*\\.aif" />
+ <data android:pathPattern=".*\\.aifc" />
+ <data android:pathPattern=".*\\.aiff" />
+ <data android:pathPattern=".*\\.amr" />
+ <data android:pathPattern=".*\\.aob" />
+ <data android:pathPattern=".*\\.ape" />
+ <data android:pathPattern=".*\\.awb" />
+ <data android:pathPattern=".*\\.caf" />
+ <data android:pathPattern=".*\\.dts" />
+ <data android:pathPattern=".*\\.flac" />
+ <data android:pathPattern=".*\\.it" />
+ <data android:pathPattern=".*\\.m4a" />
+ <data android:pathPattern=".*\\.m4b" />
+ <data android:pathPattern=".*\\.m4p" />
+ <data android:pathPattern=".*\\.mid" />
+ <data android:pathPattern=".*\\.mka" />
+ <data android:pathPattern=".*\\.mlp" />
+ <data android:pathPattern=".*\\.mod" />
+ <data android:pathPattern=".*\\.mpa" />
+ <data android:pathPattern=".*\\.mp1" />
+ <data android:pathPattern=".*\\.mp2" />
+ <data android:pathPattern=".*\\.mp3" />
+ <data android:pathPattern=".*\\.mpc" />
+ <data android:pathPattern=".*\\.mpga" />
+ <data android:pathPattern=".*\\.oga" />
+ <data android:pathPattern=".*\\.ogg" />
+ <data android:pathPattern=".*\\.oma" />
+ <data android:pathPattern=".*\\.opus" />
+ <data android:pathPattern=".*\\.ra" />
+ <data android:pathPattern=".*\\.ram" />
+ <data android:pathPattern=".*\\.rmi" />
+ <data android:pathPattern=".*\\.s3m" />
+ <data android:pathPattern=".*\\.spx" />
+ <data android:pathPattern=".*\\.tta" />
+ <data android:pathPattern=".*\\.voc" />
+ <data android:pathPattern=".*\\.vqf" />
+ <data android:pathPattern=".*\\.w64" />
+ <data android:pathPattern=".*\\.wav" />
+ <data android:pathPattern=".*\\.wma" />
+ <data android:pathPattern=".*\\.wv" />
+ <data android:pathPattern=".*\\.xa" />
+ <data android:pathPattern=".*\\.xm" />
+ <data android:pathPattern=".*\\.3GA" />
+ <data android:pathPattern=".*\\.A52" />
+ <data android:pathPattern=".*\\.AAC" />
+ <data android:pathPattern=".*\\.AC3" />
+ <data android:pathPattern=".*\\.ADT" />
+ <data android:pathPattern=".*\\.ADTS" />
+ <data android:pathPattern=".*\\.AIF" />
+ <data android:pathPattern=".*\\.AIFC" />
+ <data android:pathPattern=".*\\.AIFF" />
+ <data android:pathPattern=".*\\.AMR" />
+ <data android:pathPattern=".*\\.AOB" />
+ <data android:pathPattern=".*\\.APE" />
+ <data android:pathPattern=".*\\.AWB" />
+ <data android:pathPattern=".*\\.CAF" />
+ <data android:pathPattern=".*\\.DTS" />
+ <data android:pathPattern=".*\\.FLAC" />
+ <data android:pathPattern=".*\\.IT" />
+ <data android:pathPattern=".*\\.M4A" />
+ <data android:pathPattern=".*\\.M4B" />
+ <data android:pathPattern=".*\\.M4P" />
+ <data android:pathPattern=".*\\.MID" />
+ <data android:pathPattern=".*\\.MKA" />
+ <data android:pathPattern=".*\\.MLP" />
+ <data android:pathPattern=".*\\.MOD" />
+ <data android:pathPattern=".*\\.MPA" />
+ <data android:pathPattern=".*\\.MP1" />
+ <data android:pathPattern=".*\\.MP2" />
+ <data android:pathPattern=".*\\.MP3" />
+ <data android:pathPattern=".*\\.MPC" />
+ <data android:pathPattern=".*\\.MPGA" />
+ <data android:pathPattern=".*\\.OGA" />
+ <data android:pathPattern=".*\\.OGG" />
+ <data android:pathPattern=".*\\.OMA" />
+ <data android:pathPattern=".*\\.OPUS" />
+ <data android:pathPattern=".*\\.RA" />
+ <data android:pathPattern=".*\\.RAM" />
+ <data android:pathPattern=".*\\.RMI" />
+ <data android:pathPattern=".*\\.S3M" />
+ <data android:pathPattern=".*\\.SPX" />
+ <data android:pathPattern=".*\\.TTA" />
+ <data android:pathPattern=".*\\.VOC" />
+ <data android:pathPattern=".*\\.VQF" />
+ <data android:pathPattern=".*\\.W64" />
+ <data android:pathPattern=".*\\.WAV" />
+ <data android:pathPattern=".*\\.WMA" />
+ <data android:pathPattern=".*\\.WV" />
+ <data android:pathPattern=".*\\.XA" />
+ <data android:pathPattern=".*\\.XM" />
+ </intent-filter>
+
+ <!-- Remote URI schemes without types -->
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+
+ <data android:scheme="rtmp" />
+ <data android:scheme="rtmpt" />
+ <data android:scheme="rtmps" />
+ <data android:scheme="rtmpe" />
+ <data android:scheme="rtmfp" />
+ <data android:scheme="rtmpte" />
+ <data android:scheme="rtmpts" />
+ <data android:scheme="rtsp" />
+ <data android:scheme="rtspu" />
+ <data android:scheme="rtspt" />
+ <data android:scheme="rtsph" />
+ <data android:scheme="rtsp-sdp" />
+ <data android:scheme="rtsps" />
+ <data android:scheme="rtspsu" />
+ <data android:scheme="rtspst" />
+ <data android:scheme="rtspsh" />
+ <data android:scheme="mms" />
+ <data android:scheme="mmsh" />
+ <data android:scheme="mmst" />
+ <data android:scheme="mmsu" />
+ <data android:scheme="icy" />
+ <data android:scheme="icyx" />
+ <data android:scheme="udp" />
+ </intent-filter>
+
+ <!-- Remote URI schemes with types -->
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+
+ <data android:mimeType="audio/*" />
+ <data android:mimeType="video/*" />
+ <data android:mimeType="image/*" />
+
+ <data android:scheme="rtmp" />
+ <data android:scheme="rtmpt" />
+ <data android:scheme="rtmps" />
+ <data android:scheme="rtmpe" />
+ <data android:scheme="rtmfp" />
+ <data android:scheme="rtmpte" />
+ <data android:scheme="rtmpts" />
+ <data android:scheme="rtsp" />
+ <data android:scheme="rtspu" />
+ <data android:scheme="rtspt" />
+ <data android:scheme="rtsph" />
+ <data android:scheme="rtsp-sdp" />
+ <data android:scheme="rtsps" />
+ <data android:scheme="rtspsu" />
+ <data android:scheme="rtspst" />
+ <data android:scheme="rtspsh" />
+ <data android:scheme="mms" />
+ <data android:scheme="mmsh" />
+ <data android:scheme="mmst" />
+ <data android:scheme="mmsu" />
+ <data android:scheme="icy" />
+ <data android:scheme="icyx" />
+ <data android:scheme="udp" />
+ </intent-filter>
+
+ </activity>
+ </application>
+</manifest>
diff --git a/playback/player/android/app/src/main/java/org/freedesktop/gstreamer/Player.java b/playback/player/android/app/src/main/java/org/freedesktop/gstreamer/Player.java
new file mode 100644
index 0000000..e2bef8c
--- /dev/null
+++ b/playback/player/android/app/src/main/java/org/freedesktop/gstreamer/Player.java
@@ -0,0 +1,241 @@
+/* GStreamer
+ *
+ * Copyright (C) 2014-2015 Sebastian Dröge <sebastian@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+package org.freedesktop.gstreamer;
+
+import java.io.Closeable;
+import android.view.Surface;
+import android.content.Context;
+import org.freedesktop.gstreamer.GStreamer;
+
+public class Player implements Closeable {
+ private static native void nativeClassInit();
+ public static void init(Context context) throws Exception {
+ System.loadLibrary("gstreamer_android");
+ GStreamer.init(context);
+
+ System.loadLibrary("gstplayer");
+ nativeClassInit();
+ }
+
+ private long native_player;
+ private native void nativeNew();
+ public Player() {
+ nativeNew();
+ }
+
+ private native void nativeFree();
+ @Override
+ public void close() {
+ nativeFree();
+ }
+
+ private native void nativePlay();
+ public void play() {
+ nativePlay();
+ }
+
+ private native void nativePause();
+ public void pause() {
+ nativePause();
+ }
+
+ private native void nativeStop();
+ public void stop() {
+ nativeStop();
+ }
+
+ private native void nativeSeek(long position);
+ public void seek(long position) {
+ nativeSeek(position);
+ }
+
+ private native String nativeGetUri();
+ public String getUri() {
+ return nativeGetUri();
+ }
+
+ private native void nativeSetUri(String uri);
+ public void setUri(String uri) {
+ nativeSetUri(uri);
+ }
+
+ private native long nativeGetPosition();
+ public long getPosition() {
+ return nativeGetPosition();
+ }
+
+ private native long nativeGetDuration();
+ public long getDuration() {
+ return nativeGetDuration();
+ }
+
+ private native double nativeGetVolume();
+ public double getVolume() {
+ return nativeGetVolume();
+ }
+
+ private native void nativeSetVolume(double volume);
+ public void setVolume(double volume) {
+ nativeSetVolume(volume);
+ }
+
+ private native boolean nativeGetMute();
+ public boolean getMute() {
+ return nativeGetMute();
+ }
+
+ private native void nativeSetMute(boolean mute);
+ public void setMute(boolean mute) {
+ nativeSetMute(mute);
+ }
+
+ private Surface surface;
+ private native void nativeSetSurface(Surface surface);
+ public void setSurface(Surface surface) {
+ this.surface = surface;
+ nativeSetSurface(surface);
+ }
+
+ public Surface getSurface() {
+ return surface;
+ }
+
+ public static interface PositionUpdatedListener {
+ abstract void positionUpdated(Player player, long position);
+ }
+
+ private PositionUpdatedListener positionUpdatedListener;
+ public void setPositionUpdatedListener(PositionUpdatedListener listener) {
+ positionUpdatedListener = listener;
+ }
+
+ private void onPositionUpdated(long position) {
+ if (positionUpdatedListener != null) {
+ positionUpdatedListener.positionUpdated(this, position);
+ }
+ }
+
+ public static interface DurationChangedListener {
+ abstract void durationChanged(Player player, long duration);
+ }
+
+ private DurationChangedListener durationChangedListener;
+ public void setDurationChangedListener(DurationChangedListener listener) {
+ durationChangedListener = listener;
+ }
+
+ private void onDurationChanged(long duration) {
+ if (durationChangedListener != null) {
+ durationChangedListener.durationChanged(this, duration);
+ }
+ }
+
+ private static final State[] stateMap = {State.STOPPED, State.BUFFERING, State.PAUSED, State.PLAYING};
+ public enum State {
+ STOPPED,
+ BUFFERING,
+ PAUSED,
+ PLAYING
+ }
+
+ public static interface StateChangedListener {
+ abstract void stateChanged(Player player, State state);
+ }
+
+ private StateChangedListener stateChangedListener;
+ public void setStateChangedListener(StateChangedListener listener) {
+ stateChangedListener = listener;
+ }
+
+ private void onStateChanged(int stateIdx) {
+ if (stateChangedListener != null) {
+ State state = stateMap[stateIdx];
+ stateChangedListener.stateChanged(this, state);
+ }
+ }
+
+ public static interface BufferingListener {
+ abstract void buffering(Player player, int percent);
+ }
+
+ private BufferingListener bufferingListener;
+ public void setBufferingListener(BufferingListener listener) {
+ bufferingListener = listener;
+ }
+
+ private void onBuffering(int percent) {
+ if (bufferingListener != null) {
+ bufferingListener.buffering(this, percent);
+ }
+ }
+
+ public static interface EndOfStreamListener {
+ abstract void endOfStream(Player player);
+ }
+
+ private EndOfStreamListener endOfStreamListener;
+ public void setEndOfStreamListener(EndOfStreamListener listener) {
+ endOfStreamListener = listener;
+ }
+
+ private void onEndOfStream() {
+ if (endOfStreamListener != null) {
+ endOfStreamListener.endOfStream(this);
+ }
+ }
+
+ // Keep these in sync with gstplayer.h
+ private static final Error[] errorMap = {Error.FAILED};
+ public enum Error {
+ FAILED
+ }
+
+ public static interface ErrorListener {
+ abstract void error(Player player, Error error, String errorMessage);
+ }
+
+ private ErrorListener errorListener;
+ public void setErrorListener(ErrorListener listener) {
+ errorListener = listener;
+ }
+
+ private void onError(int errorCode, String errorMessage) {
+ if (errorListener != null) {
+ Error error = errorMap[errorCode];
+ errorListener.error(this, error, errorMessage);
+ }
+ }
+
+ public static interface VideoDimensionsChangedListener {
+ abstract void videoDimensionsChanged(Player player, int width, int height);
+ }
+
+ private VideoDimensionsChangedListener videoDimensionsChangedListener;
+ public void setVideoDimensionsChangedListener(VideoDimensionsChangedListener listener) {
+ videoDimensionsChangedListener = listener;
+ }
+
+ private void onVideoDimensionsChanged(int width, int height) {
+ if (videoDimensionsChangedListener != null) {
+ videoDimensionsChangedListener.videoDimensionsChanged(this, width, height);
+ }
+ }
+}
diff --git a/playback/player/android/app/src/main/java/org/freedesktop/gstreamer/player/GStreamerSurfaceView.java b/playback/player/android/app/src/main/java/org/freedesktop/gstreamer/player/GStreamerSurfaceView.java
new file mode 100644
index 0000000..075f035
--- /dev/null
+++ b/playback/player/android/app/src/main/java/org/freedesktop/gstreamer/player/GStreamerSurfaceView.java
@@ -0,0 +1,110 @@
+/* GStreamer
+ *
+ * Copyright (C) 2014 Sebastian Dröge <sebastian@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+package org.freedesktop.gstreamer.play;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.SurfaceView;
+import android.view.View;
+
+// A simple SurfaceView whose width and height can be set from the outside
+public class GStreamerSurfaceView extends SurfaceView {
+ public int media_width = 320;
+ public int media_height = 240;
+
+ // Mandatory constructors, they do not do much
+ public GStreamerSurfaceView(Context context, AttributeSet attrs,
+ int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public GStreamerSurfaceView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public GStreamerSurfaceView (Context context) {
+ super(context);
+ }
+
+ // Called by the layout manager to find out our size and give us some rules.
+ // We will try to maximize our size, and preserve the media's aspect ratio if
+ // we are given the freedom to do so.
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ if (media_width == 0 || media_height == 0) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ return;
+ }
+
+ int width = 0, height = 0;
+ int wmode = View.MeasureSpec.getMode(widthMeasureSpec);
+ int hmode = View.MeasureSpec.getMode(heightMeasureSpec);
+ int wsize = View.MeasureSpec.getSize(widthMeasureSpec);
+ int hsize = View.MeasureSpec.getSize(heightMeasureSpec);
+
+ Log.i ("GStreamer", "onMeasure called with " + media_width + "x" + media_height);
+ // Obey width rules
+ switch (wmode) {
+ case View.MeasureSpec.AT_MOST:
+ if (hmode == View.MeasureSpec.EXACTLY) {
+ width = Math.min(hsize * media_width / media_height, wsize);
+ break;
+ }
+ case View.MeasureSpec.EXACTLY:
+ width = wsize;
+ break;
+ case View.MeasureSpec.UNSPECIFIED:
+ width = media_width;
+ }
+
+ // Obey height rules
+ switch (hmode) {
+ case View.MeasureSpec.AT_MOST:
+ if (wmode == View.MeasureSpec.EXACTLY) {
+ height = Math.min(wsize * media_height / media_width, hsize);
+ break;
+ }
+ case View.MeasureSpec.EXACTLY:
+ height = hsize;
+ break;
+ case View.MeasureSpec.UNSPECIFIED:
+ height = media_height;
+ }
+
+ // Finally, calculate best size when both axis are free
+ if (hmode == View.MeasureSpec.AT_MOST && wmode == View.MeasureSpec.AT_MOST) {
+ int correct_height = width * media_height / media_width;
+ int correct_width = height * media_width / media_height;
+
+ if (correct_height < height)
+ height = correct_height;
+ else
+ width = correct_width;
+ }
+
+ // Obey minimum size
+ width = Math.max (getSuggestedMinimumWidth(), width);
+ height = Math.max (getSuggestedMinimumHeight(), height);
+ setMeasuredDimension(width, height);
+ }
+
+}
diff --git a/playback/player/android/app/src/main/java/org/freedesktop/gstreamer/player/Play.java b/playback/player/android/app/src/main/java/org/freedesktop/gstreamer/player/Play.java
new file mode 100644
index 0000000..2874f05
--- /dev/null
+++ b/playback/player/android/app/src/main/java/org/freedesktop/gstreamer/player/Play.java
@@ -0,0 +1,196 @@
+/* GStreamer
+ *
+ * Copyright (C) 2014 Sebastian Dröge <sebastian@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+package org.freedesktop.gstreamer.play;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TimeZone;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.ImageButton;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import org.freedesktop.gstreamer.Player;
+
+public class Play extends Activity implements SurfaceHolder.Callback, OnSeekBarChangeListener {
+ private PowerManager.WakeLock wake_lock;
+ private Player player;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+
+ try {
+ Player.init(this);
+ } catch (Exception e) {
+ Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show();
+ finish();
+ return;
+ }
+
+ setContentView(R.layout.main);
+
+ player = new Player();
+
+ PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
+ wake_lock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, "GStreamer Play");
+ wake_lock.setReferenceCounted(false);
+
+ ImageButton play = (ImageButton) this.findViewById(R.id.button_play);
+ play.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ player.play();
+ wake_lock.acquire();
+ }
+ });
+
+ ImageButton pause = (ImageButton) this.findViewById(R.id.button_pause);
+ pause.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ player.pause();
+ wake_lock.release();
+ }
+ });
+
+ final SeekBar sb = (SeekBar) this.findViewById(R.id.seek_bar);
+ sb.setOnSeekBarChangeListener(this);
+
+ player.setPositionUpdatedListener(new Player.PositionUpdatedListener() {
+ public void positionUpdated(Player player, final long position) {
+ runOnUiThread (new Runnable() {
+ public void run() {
+ sb.setProgress((int) (position / 1000000));
+ updateTimeWidget();
+ }
+ });
+ }
+ });
+
+ player.setDurationChangedListener(new Player.DurationChangedListener() {
+ public void durationChanged(Player player, final long duration) {
+ runOnUiThread (new Runnable() {
+ public void run() {
+ sb.setMax((int) (duration / 1000000));
+ updateTimeWidget();
+ }
+ });
+ }
+ });
+
+ final GStreamerSurfaceView gsv = (GStreamerSurfaceView) this.findViewById(R.id.surface_video);
+ player.setVideoDimensionsChangedListener(new Player.VideoDimensionsChangedListener() {
+ public void videoDimensionsChanged(Player player, final int width, final int height) {
+ runOnUiThread (new Runnable() {
+ public void run() {
+ Log.i ("GStreamer", "Media size changed to " + width + "x" + height);
+ gsv.media_width = width;
+ gsv.media_height = height;
+ runOnUiThread(new Runnable() {
+ public void run() {
+ gsv.requestLayout();
+ }
+ });
+ }
+ });
+ }
+ });
+
+ SurfaceView sv = (SurfaceView) this.findViewById(R.id.surface_video);
+ SurfaceHolder sh = sv.getHolder();
+ sh.addCallback(this);
+
+ String mediaUri = null;
+ Intent intent = getIntent();
+ android.net.Uri uri = intent.getData();
+ Log.i ("GStreamer", "Received URI: " + uri);
+ if (uri.getScheme().equals("content")) {
+ android.database.Cursor cursor = getContentResolver().query(uri, null, null, null, null);
+ cursor.moveToFirst();
+ mediaUri = "file://" + cursor.getString(cursor.getColumnIndex(android.provider.MediaStore.Video.Media.DATA));
+ cursor.close();
+ } else {
+ mediaUri = uri.toString();
+ }
+ player.setUri(mediaUri);
+
+ updateTimeWidget();
+ }
+
+ protected void onDestroy() {
+ player.close();
+ super.onDestroy();
+ }
+
+ private void updateTimeWidget () {
+ final TextView tv = (TextView) this.findViewById(R.id.textview_time);
+ final SeekBar sb = (SeekBar) this.findViewById(R.id.seek_bar);
+ final int pos = sb.getProgress();
+ final int max = sb.getMax();
+
+ SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss");
+ df.setTimeZone(TimeZone.getTimeZone("UTC"));
+ final String message = df.format(new Date (pos)) + " / " + df.format(new Date (max));
+ tv.setText(message);
+ }
+
+ public void surfaceChanged(SurfaceHolder holder, int format, int width,
+ int height) {
+ Log.d("GStreamer", "Surface changed to format " + format + " width "
+ + width + " height " + height);
+ player.setSurface(holder.getSurface());
+ }
+
+ public void surfaceCreated(SurfaceHolder holder) {
+ Log.d("GStreamer", "Surface created: " + holder.getSurface());
+ }
+
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ Log.d("GStreamer", "Surface destroyed");
+ player.setSurface(null);
+ }
+
+ public void onProgressChanged(SeekBar sb, int progress, boolean fromUser) {
+ if (!fromUser) return;
+
+ updateTimeWidget();
+ }
+
+ public void onStartTrackingTouch(SeekBar sb) {
+ }
+
+ public void onStopTrackingTouch(SeekBar sb) {
+ Log.d("GStreamer", "Seek to " + sb.getProgress());
+ player.seek(((long) sb.getProgress()) * 1000000);
+ }
+}
diff --git a/playback/player/android/app/src/main/jni/Android.mk b/playback/player/android/app/src/main/jni/Android.mk
new file mode 100644
index 0000000..65e2044
--- /dev/null
+++ b/playback/player/android/app/src/main/jni/Android.mk
@@ -0,0 +1,39 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := gstplayer
+LOCAL_SRC_FILES := player.c
+
+LOCAL_SHARED_LIBRARIES := gstreamer_android
+LOCAL_LDLIBS := -llog -landroid
+include $(BUILD_SHARED_LIBRARY)
+
+ifeq ($(TARGET_ARCH_ABI),armeabi)
+GSTREAMER_ROOT := $(GSTREAMER_ROOT_ARM)
+else ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)
+GSTREAMER_ROOT := $(GSTREAMER_ROOT_ARMV7)
+else ifeq ($(TARGET_ARCH_ABI),arm64-v8a)
+GSTREAMER_ROOT := $(GSTREAMER_ROOT_ARM64)
+else ifeq ($(TARGET_ARCH_ABI),x86)
+GSTREAMER_ROOT := $(GSTREAMER_ROOT_X86)
+else ifeq ($(TARGET_ARCH_ABI),x86_64)
+GSTREAMER_ROOT := $(GSTREAMER_ROOT_X86_64)
+else
+$(error Target arch ABI not supported)
+endif
+
+ifndef GSTREAMER_ROOT
+ifndef GSTREAMER_ROOT_ANDROID
+$(error GSTREAMER_ROOT_ANDROID is not defined!)
+endif
+GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)
+endif
+
+GSTREAMER_NDK_BUILD_PATH := $(GSTREAMER_ROOT)/share/gst-android/ndk-build/
+
+include $(GSTREAMER_NDK_BUILD_PATH)/plugins.mk
+GSTREAMER_PLUGINS := $(GSTREAMER_PLUGINS_CORE) $(GSTREAMER_PLUGINS_PLAYBACK) $(GSTREAMER_PLUGINS_CODECS) $(GSTREAMER_PLUGINS_NET) $(GSTREAMER_PLUGINS_SYS) $(GSTREAMER_PLUGINS_CODECS_RESTRICTED) $(GSTREAMER_CODECS_GPL) $(GSTREAMER_PLUGINS_ENCODING) $(GSTREAMER_PLUGINS_VIS) $(GSTREAMER_PLUGINS_EFFECTS) $(GSTREAMER_PLUGINS_NET_RESTRICTED)
+GSTREAMER_EXTRA_DEPS := gstreamer-player-1.0 gstreamer-video-1.0 glib-2.0
+
+include $(GSTREAMER_NDK_BUILD_PATH)/gstreamer-1.0.mk
diff --git a/playback/player/android/app/src/main/jni/Application.mk b/playback/player/android/app/src/main/jni/Application.mk
new file mode 100644
index 0000000..87c0990
--- /dev/null
+++ b/playback/player/android/app/src/main/jni/Application.mk
@@ -0,0 +1 @@
+APP_PLATFORM = 15
diff --git a/playback/player/android/app/src/main/jni/player.c b/playback/player/android/app/src/main/jni/player.c
new file mode 100644
index 0000000..58d177e
--- /dev/null
+++ b/playback/player/android/app/src/main/jni/player.c
@@ -0,0 +1,497 @@
+/* GStreamer
+ *
+ * Copyright (C) 2014 Sebastian Dröge <sebastian@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <string.h>
+#include <stdint.h>
+#include <jni.h>
+#include <android/log.h>
+#include <android/native_window.h>
+#include <android/native_window_jni.h>
+
+#include <gst/player/player.h>
+
+GST_DEBUG_CATEGORY_STATIC (debug_category);
+#define GST_CAT_DEFAULT debug_category
+
+#define GET_CUSTOM_DATA(env, thiz, fieldID) (Player *)(gintptr)(*env)->GetLongField (env, thiz, fieldID)
+#define SET_CUSTOM_DATA(env, thiz, fieldID, data) (*env)->SetLongField (env, thiz, fieldID, (jlong)(gintptr)data)
+
+typedef struct _Player
+{
+ jobject java_player;
+ GstPlayer *player;
+ GstPlayerVideoRenderer *renderer;
+ ANativeWindow *native_window;
+} Player;
+
+static pthread_key_t current_jni_env;
+static JavaVM *java_vm;
+static jfieldID native_player_field_id;
+static jmethodID on_position_updated_method_id;
+static jmethodID on_duration_changed_method_id;
+static jmethodID on_state_changed_method_id;
+static jmethodID on_buffering_method_id;
+static jmethodID on_end_of_stream_method_id;
+static jmethodID on_error_method_id;
+static jmethodID on_video_dimensions_changed_method_id;
+
+/* Register this thread with the VM */
+static JNIEnv *
+attach_current_thread (void)
+{
+ JNIEnv *env;
+ JavaVMAttachArgs args;
+
+ GST_DEBUG ("Attaching thread %p", g_thread_self ());
+ args.version = JNI_VERSION_1_4;
+ args.name = NULL;
+ args.group = NULL;
+
+ if ((*java_vm)->AttachCurrentThread (java_vm, &env, &args) < 0) {
+ GST_ERROR ("Failed to attach current thread");
+ return NULL;
+ }
+
+ return env;
+}
+
+/* Unregister this thread from the VM */
+static void
+detach_current_thread (void *env)
+{
+ GST_DEBUG ("Detaching thread %p", g_thread_self ());
+ (*java_vm)->DetachCurrentThread (java_vm);
+}
+
+/* Retrieve the JNI environment for this thread */
+static JNIEnv *
+get_jni_env (void)
+{
+ JNIEnv *env;
+
+ if ((env = pthread_getspecific (current_jni_env)) == NULL) {
+ env = attach_current_thread ();
+ pthread_setspecific (current_jni_env, env);
+ }
+
+ return env;
+}
+
+/*
+ * Java Bindings
+ */
+static void
+on_position_updated (GstPlayer * unused, GstClockTime position, Player * player)
+{
+ JNIEnv *env = get_jni_env ();
+
+ (*env)->CallVoidMethod (env, player->java_player,
+ on_position_updated_method_id, position);
+ if ((*env)->ExceptionCheck (env)) {
+ (*env)->ExceptionDescribe (env);
+ (*env)->ExceptionClear (env);
+ }
+}
+
+static void
+on_duration_changed (GstPlayer * unused, GstClockTime duration, Player * player)
+{
+ JNIEnv *env = get_jni_env ();
+
+ (*env)->CallVoidMethod (env, player->java_player,
+ on_duration_changed_method_id, duration);
+ if ((*env)->ExceptionCheck (env)) {
+ (*env)->ExceptionDescribe (env);
+ (*env)->ExceptionClear (env);
+ }
+}
+
+static void
+on_state_changed (GstPlayer * unused, GstPlayerState state, Player * player)
+{
+ JNIEnv *env = get_jni_env ();
+
+ (*env)->CallVoidMethod (env, player->java_player,
+ on_state_changed_method_id, state);
+ if ((*env)->ExceptionCheck (env)) {
+ (*env)->ExceptionDescribe (env);
+ (*env)->ExceptionClear (env);
+ }
+}
+
+static void
+on_buffering (GstPlayer * unused, gint percent, Player * player)
+{
+ JNIEnv *env = get_jni_env ();
+
+ (*env)->CallVoidMethod (env, player->java_player,
+ on_buffering_method_id, percent);
+ if ((*env)->ExceptionCheck (env)) {
+ (*env)->ExceptionDescribe (env);
+ (*env)->ExceptionClear (env);
+ }
+}
+
+static void
+on_end_of_stream (GstPlayer * unused, Player * player)
+{
+ JNIEnv *env = get_jni_env ();
+
+ (*env)->CallVoidMethod (env, player->java_player, on_end_of_stream_method_id);
+ if ((*env)->ExceptionCheck (env)) {
+ (*env)->ExceptionDescribe (env);
+ (*env)->ExceptionClear (env);
+ }
+}
+
+static void
+on_error (GstPlayer * unused, GError * err, Player * player)
+{
+ JNIEnv *env = get_jni_env ();
+ jstring error_msg;
+
+ error_msg = (*env)->NewStringUTF (env, err->message);
+
+ (*env)->CallVoidMethod (env, player->java_player, on_error_method_id,
+ err->code, error_msg);
+ if ((*env)->ExceptionCheck (env)) {
+ (*env)->ExceptionDescribe (env);
+ (*env)->ExceptionClear (env);
+ }
+
+ (*env)->DeleteLocalRef (env, error_msg);
+}
+
+static void
+on_video_dimensions_changed (GstPlayer * unused, gint width, gint height,
+ Player * player)
+{
+ JNIEnv *env = get_jni_env ();
+
+ (*env)->CallVoidMethod (env, player->java_player,
+ on_video_dimensions_changed_method_id, width, height);
+ if ((*env)->ExceptionCheck (env)) {
+ (*env)->ExceptionDescribe (env);
+ (*env)->ExceptionClear (env);
+ }
+}
+
+static void
+native_new (JNIEnv * env, jobject thiz)
+{
+ Player *player = g_new0 (Player, 1);
+
+ player->renderer = gst_player_video_overlay_video_renderer_new (NULL);
+ player->player = gst_player_new (player->renderer, NULL);
+ SET_CUSTOM_DATA (env, thiz, native_player_field_id, player);
+ player->java_player = (*env)->NewGlobalRef (env, thiz);
+
+ g_signal_connect (player->player, "position-updated",
+ G_CALLBACK (on_position_updated), player);
+ g_signal_connect (player->player, "duration-changed",
+ G_CALLBACK (on_duration_changed), player);
+ g_signal_connect (player->player, "state-changed",
+ G_CALLBACK (on_state_changed), player);
+ g_signal_connect (player->player, "buffering",
+ G_CALLBACK (on_buffering), player);
+ g_signal_connect (player->player, "end-of-stream",
+ G_CALLBACK (on_end_of_stream), player);
+ g_signal_connect (player->player, "error", G_CALLBACK (on_error), player);
+ g_signal_connect (player->player, "video-dimensions-changed",
+ G_CALLBACK (on_video_dimensions_changed), player);
+}
+
+static void
+native_free (JNIEnv * env, jobject thiz)
+{
+ Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
+
+ if (!player)
+ return;
+
+ g_object_unref (player->player);
+ (*env)->DeleteGlobalRef (env, player->java_player);
+ g_free (player);
+ SET_CUSTOM_DATA (env, thiz, native_player_field_id, NULL);
+}
+
+static void
+native_play (JNIEnv * env, jobject thiz)
+{
+ Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
+
+ if (!player)
+ return;
+
+ gst_player_play (player->player);
+}
+
+static void
+native_pause (JNIEnv * env, jobject thiz)
+{
+ Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
+
+ if (!player)
+ return;
+
+ gst_player_pause (player->player);
+}
+
+static void
+native_stop (JNIEnv * env, jobject thiz)
+{
+ Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
+
+ if (!player)
+ return;
+
+ gst_player_stop (player->player);
+}
+
+static void
+native_seek (JNIEnv * env, jobject thiz, jlong position)
+{
+ Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
+
+ if (!player)
+ return;
+
+ gst_player_seek (player->player, position);
+}
+
+static void
+native_set_uri (JNIEnv * env, jobject thiz, jobject uri)
+{
+ Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
+ const gchar *uri_str;
+
+ if (!player)
+ return;
+
+ uri_str = (*env)->GetStringUTFChars (env, uri, NULL);
+ g_object_set (player->player, "uri", uri_str, NULL);
+ (*env)->ReleaseStringUTFChars (env, uri, uri_str);
+}
+
+static jobject
+native_get_uri (JNIEnv * env, jobject thiz)
+{
+ Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
+ jobject uri;
+ gchar *uri_str;
+
+ if (!player)
+ return NULL;
+
+ g_object_get (player->player, "uri", &uri_str, NULL);
+
+ uri = (*env)->NewStringUTF (env, uri_str);
+ g_free (uri_str);
+
+ return uri;
+}
+
+static jlong
+native_get_position (JNIEnv * env, jobject thiz)
+{
+ Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
+ jdouble position;
+
+ if (!player)
+ return -1;
+
+ g_object_get (player->player, "position", &position, NULL);
+
+ return position;
+}
+
+static jlong
+native_get_duration (JNIEnv * env, jobject thiz)
+{
+ Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
+ jlong duration;
+
+ if (!player)
+ return -1;
+
+ g_object_get (player->player, "duration", &duration, NULL);
+
+ return duration;
+}
+
+static jdouble
+native_get_volume (JNIEnv * env, jobject thiz)
+{
+ Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
+ jdouble volume;
+
+ if (!player)
+ return 1.0;
+
+ g_object_get (player->player, "volume", &volume, NULL);
+
+ return volume;
+}
+
+static void
+native_set_volume (JNIEnv * env, jobject thiz, jdouble volume)
+{
+ Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
+
+ if (!player)
+ return;
+
+ g_object_set (player->player, "volume", volume, NULL);
+}
+
+static jboolean
+native_get_mute (JNIEnv * env, jobject thiz)
+{
+ Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
+ jboolean mute;
+
+ if (!player)
+ return FALSE;
+
+ g_object_get (player->player, "mute", &mute, NULL);
+
+ return mute;
+}
+
+static void
+native_set_mute (JNIEnv * env, jobject thiz, jboolean mute)
+{
+ Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
+
+ if (!player)
+ return;
+
+ g_object_set (player->player, "mute", mute, NULL);
+}
+
+static void
+native_set_surface (JNIEnv * env, jobject thiz, jobject surface)
+{
+ Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
+ ANativeWindow *new_native_window;
+
+ if (!player)
+ return;
+
+ new_native_window = surface ? ANativeWindow_fromSurface (env, surface) : NULL;
+ GST_DEBUG ("Received surface %p (native window %p)", surface,
+ new_native_window);
+
+ if (player->native_window) {
+ ANativeWindow_release (player->native_window);
+ }
+
+ player->native_window = new_native_window;
+ gst_player_video_overlay_video_renderer_set_window_handle
+ (GST_PLAYER_VIDEO_OVERLAY_VIDEO_RENDERER (player->renderer),
+ (gpointer) new_native_window);
+}
+
+static void
+native_class_init (JNIEnv * env, jclass klass)
+{
+ native_player_field_id =
+ (*env)->GetFieldID (env, klass, "native_player", "J");
+ on_position_updated_method_id =
+ (*env)->GetMethodID (env, klass, "onPositionUpdated", "(J)V");
+ on_duration_changed_method_id =
+ (*env)->GetMethodID (env, klass, "onDurationChanged", "(J)V");
+ on_state_changed_method_id =
+ (*env)->GetMethodID (env, klass, "onStateChanged", "(I)V");
+ on_buffering_method_id =
+ (*env)->GetMethodID (env, klass, "onBuffering", "(I)V");
+ on_end_of_stream_method_id =
+ (*env)->GetMethodID (env, klass, "onEndOfStream", "()V");
+ on_error_method_id =
+ (*env)->GetMethodID (env, klass, "onError", "(ILjava/lang/String;)V");
+ on_video_dimensions_changed_method_id =
+ (*env)->GetMethodID (env, klass, "onVideoDimensionsChanged", "(II)V");
+
+ if (!native_player_field_id ||
+ !on_position_updated_method_id || !on_duration_changed_method_id ||
+ !on_state_changed_method_id || !on_buffering_method_id ||
+ !on_end_of_stream_method_id ||
+ !on_error_method_id || !on_video_dimensions_changed_method_id) {
+ static const gchar *message =
+ "The calling class does not implement all necessary interface methods";
+ jclass exception_class = (*env)->FindClass (env, "java/lang/Exception");
+ __android_log_print (ANDROID_LOG_ERROR, "GstPlayer", "%s", message);
+ (*env)->ThrowNew (env, exception_class, message);
+ }
+
+ gst_debug_set_threshold_for_name ("gst-player", GST_LEVEL_TRACE);
+}
+
+/* List of implemented native methods */
+static JNINativeMethod native_methods[] = {
+ {"nativeClassInit", "()V", (void *) native_class_init},
+ {"nativeNew", "()V", (void *) native_new},
+ {"nativePlay", "()V", (void *) native_play},
+ {"nativePause", "()V", (void *) native_pause},
+ {"nativeStop", "()V", (void *) native_stop},
+ {"nativeSeek", "(J)V", (void *) native_seek},
+ {"nativeFree", "()V", (void *) native_free},
+ {"nativeGetUri", "()Ljava/lang/String;", (void *) native_get_uri},
+ {"nativeSetUri", "(Ljava/lang/String;)V", (void *) native_set_uri},
+ {"nativeGetPosition", "()J", (void *) native_get_position},
+ {"nativeGetDuration", "()J", (void *) native_get_duration},
+ {"nativeGetVolume", "()D", (void *) native_get_volume},
+ {"nativeSetVolume", "(D)V", (void *) native_set_volume},
+ {"nativeGetMute", "()Z", (void *) native_get_mute},
+ {"nativeSetMute", "(Z)V", (void *) native_set_mute},
+ {"nativeSetSurface", "(Landroid/view/Surface;)V",
+ (void *) native_set_surface}
+};
+
+/* Library initializer */
+jint
+JNI_OnLoad (JavaVM * vm, void *reserved)
+{
+ JNIEnv *env = NULL;
+
+ java_vm = vm;
+
+ if ((*vm)->GetEnv (vm, (void **) &env, JNI_VERSION_1_4) != JNI_OK) {
+ __android_log_print (ANDROID_LOG_ERROR, "GstPlayer",
+ "Could not retrieve JNIEnv");
+ return 0;
+ }
+ jclass klass = (*env)->FindClass (env, "org/freedesktop/gstreamer/Player");
+ if (!klass) {
+ __android_log_print (ANDROID_LOG_ERROR, "GstPlayer",
+ "Could not retrieve class org.freedesktop.gstreamer.Player");
+ return 0;
+ }
+ if ((*env)->RegisterNatives (env, klass, native_methods,
+ G_N_ELEMENTS (native_methods))) {
+ __android_log_print (ANDROID_LOG_ERROR, "GstPlayer",
+ "Could not register native methods for org.freedesktop.gstreamer.Player");
+ return 0;
+ }
+
+ pthread_key_create (&current_jni_env, detach_current_thread);
+
+ return JNI_VERSION_1_4;
+}
diff --git a/playback/player/android/app/src/main/res/layout/main.xml b/playback/player/android/app/src/main/res/layout/main.xml
new file mode 100644
index 0000000..b745d80
--- /dev/null
+++ b/playback/player/android/app/src/main/res/layout/main.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:orientation="vertical" >
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="16dip"
+ android:gravity="center_horizontal"
+ android:orientation="horizontal" >
+
+ <ImageButton
+ android:id="@+id/button_play"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:contentDescription="@string/button_play"
+ android:src="@android:drawable/ic_media_play"
+ android:text="@string/button_play" />
+
+ <ImageButton
+ android:id="@+id/button_pause"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:contentDescription="@string/button_pause"
+ android:src="@android:drawable/ic_media_pause"
+ android:text="@string/button_pause" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="16dip"
+ android:gravity="center_horizontal"
+ android:orientation="horizontal" >
+
+ <TextView
+ android:id="@+id/textview_time"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="5dip" />
+
+ <SeekBar
+ android:id="@+id/seek_bar"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_weight="1"
+ android:indeterminate="false" />
+ </LinearLayout>
+
+ <org.freedesktop.gstreamer.play.GStreamerSurfaceView
+ android:id="@+id/surface_video"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical|center_horizontal" />
+
+</LinearLayout>
diff --git a/playback/player/android/app/src/main/res/values/strings.xml b/playback/player/android/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..9587e3c
--- /dev/null
+++ b/playback/player/android/app/src/main/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">GStreamer Play</string>
+ <string name="button_play">Play</string>
+ <string name="button_pause">Pause</string>
+</resources>