diff options
Diffstat (limited to 'playback/player/android/app/src')
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 (¤t_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> |