playback/player: Add start of an Android player

This commit is contained in:
Sebastian Dröge 2014-07-28 20:09:55 +02:00
parent 10eabda41b
commit be4f88ef41
6 changed files with 1109 additions and 0 deletions

View file

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.freedesktop.gstreamer.play"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="14"/>
<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">
<!-- <intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>-->
<!-- Local 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/*" />
</intent-filter>
<!-- Local 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:mimeType="*/*" />
<data android:pathPattern=".*\\.avi" />
<data android:pathPattern=".*\\.AVI" />
<data android:pathPattern=".*\\.mkv" />
<data android:pathPattern=".*\\.MKV" />
<data android:pathPattern=".*\\.webm" />
<data android:pathPattern=".*\\.WEBM" />
<data android:pathPattern=".*\\.ogv" />
<data android:pathPattern=".*\\.OGV" />
<data android:pathPattern=".*\\.mp4" />
<data android:pathPattern=".*\\.MP4" />
<data android:pathPattern=".*\\.mov" />
<data android:pathPattern=".*\\.MOV" />
</intent-filter>
<!-- Remote files. These typically have 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="http" />
<data android:pathPattern=".*\\.avi" />
<data android:pathPattern=".*\\.AVI" />
<data android:pathPattern=".*\\.mkv" />
<data android:pathPattern=".*\\.MKV" />
<data android:pathPattern=".*\\.webm" />
<data android:pathPattern=".*\\.WEBM" />
<data android:pathPattern=".*\\.ogv" />
<data android:pathPattern=".*\\.OGV" />
<data android:pathPattern=".*\\.mp4" />
<data android:pathPattern=".*\\.MP4" />
<data android:pathPattern=".*\\.mov" />
<data android:pathPattern=".*\\.MOV" />
</intent-filter>
</activity>
</application>
</manifest>

View file

@ -0,0 +1,24 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := gstplayer
LOCAL_SRC_FILES := player.c ../../lib/gstplayer.c
LOCAL_SHARED_LIBRARIES := gstreamer_android
LOCAL_LDLIBS := -llog -landroid
include $(BUILD_SHARED_LIBRARY)
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_CODECS_RESTRICTED) $(GSTREAMER_CODECS_GPL) $(GSTREAMER_PLUGINS_ENCODING) $(GSTREAMER_PLUGINS_VIS) $(GSTREAMER_PLUGINS_EFFECTS) $(GSTREAMER_PLUGINS_NET_RESTRICTED)
GSTREAMER_EXTRA_DEPS := gstreamer-video-1.0
include $(GSTREAMER_NDK_BUILD_PATH)/gstreamer-1.0.mk

View file

@ -0,0 +1,489 @@
/* 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 "../../lib/gstplayer.h"
GST_DEBUG_CATEGORY_STATIC (debug_category);
#define GST_CAT_DEFAULT debug_category
/*
* These macros provide a way to store the native pointer to Player, which might be 32 or 64 bits, into
* a jlong, which is always 64 bits, without warnings.
*/
#if GLIB_SIZEOF_VOID_P == 8
# define GET_CUSTOM_DATA(env, thiz, fieldID) (Player *)(*env)->GetLongField (env, thiz, fieldID)
# define SET_CUSTOM_DATA(env, thiz, fieldID, data) (*env)->SetLongField (env, thiz, fieldID, (jlong)data)
#else
# define GET_CUSTOM_DATA(env, thiz, fieldID) (Player *)(jint)(*env)->GetLongField (env, thiz, fieldID)
# define SET_CUSTOM_DATA(env, thiz, fieldID, data) (*env)->SetLongField (env, thiz, fieldID, (jlong)(jint)data)
#endif
typedef struct _Player
{
jobject java_player;
GstPlayer *player;
ANativeWindow *native_window;
} Player;
static pthread_t gst_app_thread;
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_end_of_stream_method_id;
static jmethodID on_seek_finished_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_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_seek_finished (GstPlayer * unused, Player * player)
{
JNIEnv *env = get_jni_env ();
(*env)->CallVoidMethod (env, player->java_player, on_seek_finished_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;
// FIXME
error_msg = err ? (*env)->NewStringUTF (env, err->message) : NULL;
(*env)->CallVoidMethod (env, player->java_player, on_error_method_id,
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_slice_new0 (Player);
player->player = gst_player_new (FALSE);
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, "end-of-stream",
G_CALLBACK (on_end_of_stream), player);
g_signal_connect (player->player, "seek-finished",
G_CALLBACK (on_seek_finished), 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;
(*env)->DeleteGlobalRef (env, player->java_player);
g_slice_free (Player, 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 jboolean
native_is_playing (JNIEnv * env, jobject thiz)
{
Player *player = GET_CUSTOM_DATA (env, thiz, native_player_field_id);
jboolean is_playing;
if (!player)
return FALSE;
g_object_get (player->player, "is-playing", &is_playing, NULL);
return is_playing;
}
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;
g_object_set (player->player, "window-handle", (gpointer) new_native_window,
NULL);
}
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_end_of_stream_method_id =
(*env)->GetMethodID (env, klass, "onEndOfStream", "()V");
on_seek_finished_method_id =
(*env)->GetMethodID (env, klass, "onSeekFinished", "()V");
on_error_method_id =
(*env)->GetMethodID (env, klass, "onError", "(Ljava/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_end_of_stream_method_id || !on_seek_finished_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},
{"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},
{"nativeIsPlaying", "()Z", (void *) native_is_playing},
{"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");
(*env)->RegisterNatives (env, klass, native_methods,
G_N_ELEMENTS (native_methods));
pthread_key_create (&current_jni_env, detach_current_thread);
return JNI_VERSION_1_4;
}

View file

@ -0,0 +1,216 @@
/* 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;
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 boolean nativeIsPlaying();
public boolean isPlaying() {
return nativeIsPlaying();
}
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);
}
}
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);
}
}
public static interface SeekFinishedListener {
abstract void seekFinished(Player player);
}
private SeekFinishedListener seekFinishedListener;
public void setSeekFinishedListener(SeekFinishedListener listener) {
seekFinishedListener = listener;
}
private void onSeekFinished() {
if (seekFinishedListener != null) {
seekFinishedListener.seekFinished(this);
}
}
public static interface ErrorListener {
// FIXME: enums
abstract void error(Player player, String errorMessage);
}
private ErrorListener errorListener;
public void setErrorListener(ErrorListener listener) {
errorListener = listener;
}
private void onError(String errorMessage) {
if (errorListener != null) {
errorListener.error(this, 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);
}
}
}

View file

@ -0,0 +1,105 @@
/* 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) {
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);
}
}

View file

@ -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);
}
}