mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-11 11:51:34 +00:00
playback/player: Add start of an Android player
This commit is contained in:
parent
10eabda41b
commit
be4f88ef41
6 changed files with 1109 additions and 0 deletions
79
playback/player/android/AndroidManifest.xml
Normal file
79
playback/player/android/AndroidManifest.xml
Normal 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>
|
24
playback/player/android/jni/Android.mk
Normal file
24
playback/player/android/jni/Android.mk
Normal 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
|
489
playback/player/android/jni/player.c
Normal file
489
playback/player/android/jni/player.c
Normal 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 (¤t_jni_env, detach_current_thread);
|
||||
|
||||
return JNI_VERSION_1_4;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue