init commit
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
.gradle
|
||||||
|
build/
|
||||||
3
.idea/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
6
.idea/compiler.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CompilerConfiguration">
|
||||||
|
<bytecodeTargetLevel target="16" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
21
.idea/gradle.xml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||||
|
<component name="GradleSettings">
|
||||||
|
<option name="linkedExternalProjectsSettings">
|
||||||
|
<GradleProjectSettings>
|
||||||
|
<option name="testRunner" value="GRADLE" />
|
||||||
|
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||||
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
|
<option name="gradleJvm" value="semeru-16" />
|
||||||
|
<option name="modules">
|
||||||
|
<set>
|
||||||
|
<option value="$PROJECT_DIR$" />
|
||||||
|
<option value="$PROJECT_DIR$/app" />
|
||||||
|
<option value="$PROJECT_DIR$/gstreamer_player" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</GradleProjectSettings>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
25
.idea/jarRepositories.xml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="RemoteRepositoriesConfiguration">
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="central" />
|
||||||
|
<option name="name" value="Maven Central repository" />
|
||||||
|
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="jboss.community" />
|
||||||
|
<option name="name" value="JBoss Community repository" />
|
||||||
|
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="BintrayJCenter" />
|
||||||
|
<option name="name" value="BintrayJCenter" />
|
||||||
|
<option name="url" value="https://jcenter.bintray.com/" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="Google" />
|
||||||
|
<option name="name" value="Google" />
|
||||||
|
<option name="url" value="https://dl.google.com/dl/android/maven2/" />
|
||||||
|
</remote-repository>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
16
.idea/misc.xml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DesignSurface">
|
||||||
|
<option name="filePathToZoomLevelMap">
|
||||||
|
<map>
|
||||||
|
<entry key="..\:/Users/hisharp/Downloads/gstreamer-main/Gstreamer/app/src/main/res/layout/activity_main.xml" value="0.16574074074074074" />
|
||||||
|
<entry key="..\:/Users/hisharp/Downloads/gstreamer-main/Gstreamer/gstreamer_player/res/layout/main.xml" value="0.16574074074074074" />
|
||||||
|
<entry key="..\:/Users/hisharp/Downloads/gstreamer-main/android/android-tutorial-5/res/layout/main.xml" value="0.1" />
|
||||||
|
<entry key="..\:/Users/hisharp/Downloads/gstreamer-main/android/android-tutorial-6/res/layout/confmanager.xml" value="0.1" />
|
||||||
|
<entry key="..\:/Users/hisharp/Downloads/gstreamer-main/android/android-tutorial-6/res/layout/main.xml" value="0.15" />
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_16_PREVIEW" project-jdk-name="16" project-jdk-type="JavaSDK" />
|
||||||
|
</project>
|
||||||
6
.idea/vcs.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
199
app/.gitignore
vendored
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
/build### C++ template
|
||||||
|
# Prerequisites
|
||||||
|
*.d
|
||||||
|
|
||||||
|
# Compiled Object files
|
||||||
|
*.slo
|
||||||
|
*.lo
|
||||||
|
*.o
|
||||||
|
*.obj
|
||||||
|
|
||||||
|
# Precompiled Headers
|
||||||
|
*.gch
|
||||||
|
*.pch
|
||||||
|
|
||||||
|
# Compiled Dynamic libraries
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
*.dll
|
||||||
|
|
||||||
|
# Fortran module files
|
||||||
|
*.mod
|
||||||
|
*.smod
|
||||||
|
|
||||||
|
# Compiled Static libraries
|
||||||
|
*.lai
|
||||||
|
*.la
|
||||||
|
*.a
|
||||||
|
*.lib
|
||||||
|
|
||||||
|
# Executables
|
||||||
|
*.exe
|
||||||
|
*.out
|
||||||
|
*.app
|
||||||
|
|
||||||
|
### C template
|
||||||
|
# Prerequisites
|
||||||
|
*.d
|
||||||
|
|
||||||
|
# Object files
|
||||||
|
*.o
|
||||||
|
*.ko
|
||||||
|
*.obj
|
||||||
|
*.elf
|
||||||
|
|
||||||
|
# Linker output
|
||||||
|
*.ilk
|
||||||
|
*.map
|
||||||
|
*.exp
|
||||||
|
|
||||||
|
# Precompiled Headers
|
||||||
|
*.gch
|
||||||
|
*.pch
|
||||||
|
|
||||||
|
# Libraries
|
||||||
|
*.lib
|
||||||
|
*.a
|
||||||
|
*.la
|
||||||
|
*.lo
|
||||||
|
|
||||||
|
# Shared objects (inc. Windows DLLs)
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.so.*
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Executables
|
||||||
|
*.exe
|
||||||
|
*.out
|
||||||
|
*.app
|
||||||
|
*.i*86
|
||||||
|
*.x86_64
|
||||||
|
*.hex
|
||||||
|
|
||||||
|
# Debug files
|
||||||
|
*.dSYM/
|
||||||
|
*.su
|
||||||
|
*.idb
|
||||||
|
*.pdb
|
||||||
|
|
||||||
|
# Kernel Module Compile Results
|
||||||
|
*.mod*
|
||||||
|
*.cmd
|
||||||
|
.tmp_versions/
|
||||||
|
modules.order
|
||||||
|
Module.symvers
|
||||||
|
Mkfile.old
|
||||||
|
dkms.conf
|
||||||
|
|
||||||
|
### CMake template
|
||||||
|
CMakeLists.txt.user
|
||||||
|
CMakeCache.txt
|
||||||
|
CMakeFiles
|
||||||
|
CMakeScripts
|
||||||
|
Testing
|
||||||
|
Makefile
|
||||||
|
cmake_install.cmake
|
||||||
|
install_manifest.txt
|
||||||
|
compile_commands.json
|
||||||
|
CTestTestfile.cmake
|
||||||
|
_deps
|
||||||
|
|
||||||
|
### Example user template template
|
||||||
|
### Example user template
|
||||||
|
|
||||||
|
# IntelliJ project files
|
||||||
|
.idea
|
||||||
|
*.iml
|
||||||
|
out
|
||||||
|
gen
|
||||||
|
### Android template
|
||||||
|
# Built application files
|
||||||
|
*.apk
|
||||||
|
*.aar
|
||||||
|
*.ap_
|
||||||
|
*.aab
|
||||||
|
|
||||||
|
# Files for the ART/Dalvik VM
|
||||||
|
*.dex
|
||||||
|
|
||||||
|
# Java class files
|
||||||
|
*.class
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
bin/
|
||||||
|
gen/
|
||||||
|
out/
|
||||||
|
# Uncomment the following line in case you need and you don't have the release build type files in your app
|
||||||
|
# release/
|
||||||
|
|
||||||
|
# Gradle files
|
||||||
|
.gradle/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# Local configuration file (sdk path, etc)
|
||||||
|
local.properties
|
||||||
|
|
||||||
|
# Proguard folder generated by Eclipse
|
||||||
|
proguard/
|
||||||
|
|
||||||
|
# Log Files
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Android Studio Navigation editor temp files
|
||||||
|
.navigation/
|
||||||
|
|
||||||
|
# Android Studio captures folder
|
||||||
|
captures/
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
*.iml
|
||||||
|
.idea/workspace.xml
|
||||||
|
.idea/tasks.xml
|
||||||
|
.idea/gradle.xml
|
||||||
|
.idea/assetWizardSettings.xml
|
||||||
|
.idea/dictionaries
|
||||||
|
.idea/libraries
|
||||||
|
# Android Studio 3 in .gitignore file.
|
||||||
|
.idea/caches
|
||||||
|
.idea/modules.xml
|
||||||
|
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
|
||||||
|
.idea/navEditor.xml
|
||||||
|
|
||||||
|
# Keystore files
|
||||||
|
# Uncomment the following lines if you do not want to check your keystore files in.
|
||||||
|
#*.jks
|
||||||
|
#*.keystore
|
||||||
|
|
||||||
|
# External native build folder generated in Android Studio 2.2 and later
|
||||||
|
.externalNativeBuild
|
||||||
|
.cxx/
|
||||||
|
|
||||||
|
# Google Services (e.g. APIs or Firebase)
|
||||||
|
# google-services.json
|
||||||
|
|
||||||
|
# Freeline
|
||||||
|
freeline.py
|
||||||
|
freeline/
|
||||||
|
freeline_project_description.json
|
||||||
|
|
||||||
|
# fastlane
|
||||||
|
fastlane/report.xml
|
||||||
|
fastlane/Preview.html
|
||||||
|
fastlane/screenshots
|
||||||
|
fastlane/test_output
|
||||||
|
fastlane/readme.md
|
||||||
|
|
||||||
|
# Version control
|
||||||
|
vcs.xml
|
||||||
|
|
||||||
|
# lint
|
||||||
|
lint/intermediates/
|
||||||
|
lint/generated/
|
||||||
|
lint/outputs/
|
||||||
|
lint/tmp/
|
||||||
|
# lint/reports/
|
||||||
|
|
||||||
|
# Android Profiling
|
||||||
|
*.hprof
|
||||||
|
|
||||||
40
app/build.gradle
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
plugins {
|
||||||
|
id 'com.android.application'
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdk 32
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "com.ray650128.gstreamer_demo_app"
|
||||||
|
minSdk 26
|
||||||
|
targetSdk 32
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0"
|
||||||
|
|
||||||
|
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
viewBinding.enabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
|
||||||
|
implementation 'androidx.appcompat:appcompat:1.0.0'
|
||||||
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
|
implementation project(path: ':gstreamer_player')
|
||||||
|
testImplementation 'junit:junit:4.13.2'
|
||||||
|
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||||
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
|
||||||
|
}
|
||||||
21
app/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
package com.ray650128.gstreamer_demo_app;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.test.InstrumentationRegistry;
|
||||||
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instrumented test, which will execute on an Android device.
|
||||||
|
*
|
||||||
|
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class ExampleInstrumentedTest {
|
||||||
|
@Test
|
||||||
|
public void useAppContext() {
|
||||||
|
// Context of the app under test.
|
||||||
|
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||||
|
assertEquals("com.ray650128.app", appContext.getPackageName());
|
||||||
|
}
|
||||||
|
}
|
||||||
23
app/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.ray650128.gstreamer_demo_app">
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@style/Theme.Gstreamer">
|
||||||
|
<activity
|
||||||
|
android:name="com.ray650128.gstreamer_demo_app.MainActivity"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
@ -0,0 +1,61 @@
|
|||||||
|
package com.ray650128.gstreamer_demo_app;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
|
import com.hisharp.gstreamer_player.GstCallback;
|
||||||
|
import com.hisharp.gstreamer_player.GstLibrary;
|
||||||
|
import com.hisharp.gstreamer_player.GstStatus;
|
||||||
|
import com.ray650128.gstreamer_demo_app.databinding.ActivityMainBinding;
|
||||||
|
|
||||||
|
public class MainActivity extends AppCompatActivity implements GstCallback {
|
||||||
|
|
||||||
|
private String TAG = MainActivity.class.getSimpleName();
|
||||||
|
|
||||||
|
private ActivityMainBinding binding;
|
||||||
|
|
||||||
|
private GstLibrary gstLibrary;
|
||||||
|
|
||||||
|
private final String defaultMediaUri = "rtsp://admin:admin@192.168.0.77:554/media/video2";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
binding = ActivityMainBinding.inflate(getLayoutInflater());
|
||||||
|
setContentView(binding.getRoot());
|
||||||
|
|
||||||
|
gstLibrary = new GstLibrary(this, defaultMediaUri);
|
||||||
|
|
||||||
|
gstLibrary.setSurfaceView(binding.surfaceVideo);
|
||||||
|
gstLibrary.setOnStatusChangeListener(this);
|
||||||
|
|
||||||
|
binding.buttonPlay.setOnClickListener(view -> gstLibrary.play());
|
||||||
|
|
||||||
|
binding.buttonStop.setOnClickListener(view -> gstLibrary.stop());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
if (gstLibrary != null) {
|
||||||
|
gstLibrary.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStatus(GstStatus gstStatus) {
|
||||||
|
//Log.d(TAG, GstStatus.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessage(String message) {
|
||||||
|
Log.d(TAG, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMediaSizeChanged(int width, int height) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
30
app/src/main/res/drawable-v24/ic_launcher_foreground.xml
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient
|
||||||
|
android:endX="85.84757"
|
||||||
|
android:endY="92.4963"
|
||||||
|
android:startX="42.9492"
|
||||||
|
android:startY="49.59793"
|
||||||
|
android:type="linear">
|
||||||
|
<item
|
||||||
|
android:color="#44000000"
|
||||||
|
android:offset="0.0" />
|
||||||
|
<item
|
||||||
|
android:color="#00000000"
|
||||||
|
android:offset="1.0" />
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:strokeColor="#00000000" />
|
||||||
|
</vector>
|
||||||
170
app/src/main/res/drawable/ic_launcher_background.xml
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path
|
||||||
|
android:fillColor="#3DDC84"
|
||||||
|
android:pathData="M0,0h108v108h-108z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M9,0L9,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,0L19,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,0L29,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,0L39,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,0L49,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,0L59,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,0L69,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,0L79,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M89,0L89,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M99,0L99,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,9L108,9"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,19L108,19"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,29L108,29"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,39L108,39"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,49L108,49"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,59L108,59"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,69L108,69"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,79L108,79"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,89L108,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,99L108,99"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,29L89,29"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,39L89,39"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,49L89,49"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,59L89,59"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,69L89,69"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,79L89,79"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,19L29,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,19L39,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,19L49,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,19L59,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,19L69,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,19L79,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
</vector>
|
||||||
46
app/src/main/res/layout/activity_main.xml
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?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" >
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textview_message"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="16dip"
|
||||||
|
android:gravity="center_horizontal" />
|
||||||
|
|
||||||
|
<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_stop"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@string/button_stop"
|
||||||
|
android:src="@android:drawable/ic_media_pause"
|
||||||
|
android:text="@string/button_stop" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<com.hisharp.gstreamer_player.GStreamerSurfaceView
|
||||||
|
android:id="@+id/surface_video"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical|center_horizontal" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
||||||
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
||||||
BIN
app/src/main/res/mipmap-hdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 982 B |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
10
app/src/main/res/values-night/themes.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<!-- Base application theme. -->
|
||||||
|
<style name="Theme.Gstreamer" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||||
|
<!-- Primary brand color. -->
|
||||||
|
<item name="colorPrimary">@color/purple_200</item>
|
||||||
|
<item name="colorPrimaryDark">@color/purple_700</item>
|
||||||
|
<item name="colorAccent">@color/teal_200</item>
|
||||||
|
<!-- Customize your theme here. -->
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
10
app/src/main/res/values/colors.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="purple_200">#FFBB86FC</color>
|
||||||
|
<color name="purple_500">#FF6200EE</color>
|
||||||
|
<color name="purple_700">#FF3700B3</color>
|
||||||
|
<color name="teal_200">#FF03DAC5</color>
|
||||||
|
<color name="teal_700">#FF018786</color>
|
||||||
|
<color name="black">#FF000000</color>
|
||||||
|
<color name="white">#FFFFFFFF</color>
|
||||||
|
</resources>
|
||||||
3
app/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<resources>
|
||||||
|
<string name="app_name">app</string>
|
||||||
|
</resources>
|
||||||
10
app/src/main/res/values/themes.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<!-- Base application theme. -->
|
||||||
|
<style name="Theme.Gstreamer" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||||
|
<!-- Primary brand color. -->
|
||||||
|
<item name="colorPrimary">@color/purple_500</item>
|
||||||
|
<item name="colorPrimaryDark">@color/purple_700</item>
|
||||||
|
<item name="colorAccent">@color/teal_200</item>
|
||||||
|
<!-- Customize your theme here. -->
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
package com.ray650128.gstreamer_demo_app;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example local unit test, which will execute on the development machine (host).
|
||||||
|
*
|
||||||
|
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||||
|
*/
|
||||||
|
public class ExampleUnitTest {
|
||||||
|
@Test
|
||||||
|
public void addition_isCorrect() {
|
||||||
|
assertEquals(4, 2 + 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
25
build.gradle
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
jcenter()
|
||||||
|
google()
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:7.1.0'
|
||||||
|
|
||||||
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
|
// in the individual module build.gradle files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
jcenter()
|
||||||
|
google()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task clean(type: Delete) {
|
||||||
|
delete rootProject.buildDir
|
||||||
|
}
|
||||||
2
gradle.properties
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
android.enableJetifier=true
|
||||||
|
android.useAndroidX=true
|
||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#Sat Apr 21 19:58:19 WEST 2018
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
|
||||||
160
gradlew
vendored
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
##
|
||||||
|
## Gradle start up script for UN*X
|
||||||
|
##
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS=""
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD="maximum"
|
||||||
|
|
||||||
|
warn ( ) {
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die ( ) {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN* )
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
|
while [ -h "$PRG" ] ; do
|
||||||
|
ls=`ls -ld "$PRG"`
|
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="java"
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||||
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
|
if [ $? -eq 0 ] ; then
|
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
ulimit -n $MAX_FD
|
||||||
|
if [ $? -ne 0 ] ; then
|
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
|
if $darwin; then
|
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin, switch paths to Windows format before running java
|
||||||
|
if $cygwin ; then
|
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
|
SEP=""
|
||||||
|
for dir in $ROOTDIRSRAW ; do
|
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
|
SEP="|"
|
||||||
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=$((i+1))
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
(0) set -- ;;
|
||||||
|
(1) set -- "$args0" ;;
|
||||||
|
(2) set -- "$args0" "$args1" ;;
|
||||||
|
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||||
|
function splitJvmOpts() {
|
||||||
|
JVM_OPTS=("$@")
|
||||||
|
}
|
||||||
|
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||||
|
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||||
|
|
||||||
|
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
||||||
4
gstreamer_player/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.externalNativeBuild/
|
||||||
|
assets/
|
||||||
|
gst-build-*/
|
||||||
|
src/org/
|
||||||
10
gstreamer_player/AndroidManifest.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.hisharp.gstreamer_player" >
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
|
||||||
|
<uses-feature android:glEsVersion="0x00020000" />
|
||||||
|
|
||||||
|
</manifest>
|
||||||
70
gstreamer_player/build.gradle
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
apply plugin: 'com.android.library'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 32
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion 15
|
||||||
|
targetSdkVersion 32
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0"
|
||||||
|
|
||||||
|
externalNativeBuild {
|
||||||
|
ndkBuild {
|
||||||
|
def gstRoot
|
||||||
|
|
||||||
|
if (project.hasProperty('gstAndroidRoot'))
|
||||||
|
gstRoot = project.gstAndroidRoot
|
||||||
|
else
|
||||||
|
gstRoot = System.env.GSTREAMER_ROOT_ANDROID
|
||||||
|
|
||||||
|
if (gstRoot == null)
|
||||||
|
throw new GradleException('GSTREAMER_ROOT_ANDROID must be set, or "gstAndroidRoot" must be defined in your gradle.properties in the top level directory of the unpacked universal GStreamer Android binaries')
|
||||||
|
|
||||||
|
arguments "NDK_APPLICATION_MK=jni/Application.mk", "GSTREAMER_JAVA_SRC_DIR=src", "GSTREAMER_ROOT_ANDROID=$gstRoot", "GSTREAMER_ASSETS_DIR=src/assets"
|
||||||
|
|
||||||
|
targets "gst_player"
|
||||||
|
|
||||||
|
// All archs except MIPS and MIPS64 are supported
|
||||||
|
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
manifest.srcFile 'AndroidManifest.xml'
|
||||||
|
java.srcDirs = ['src']
|
||||||
|
resources.srcDirs = ['src']
|
||||||
|
aidl.srcDirs = ['src']
|
||||||
|
renderscript.srcDirs = ['src']
|
||||||
|
res.srcDirs = ['res']
|
||||||
|
assets.srcDirs = ['assets']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
externalNativeBuild {
|
||||||
|
ndkBuild {
|
||||||
|
path 'jni/Android.mk'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEvaluate {
|
||||||
|
if (project.hasProperty('compileDebugJavaWithJavac'))
|
||||||
|
project.compileDebugJavaWithJavac.dependsOn 'externalNativeBuildDebug'
|
||||||
|
if (project.hasProperty('compileReleaseJavaWithJavac'))
|
||||||
|
project.compileReleaseJavaWithJavac.dependsOn 'externalNativeBuildRelease'
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
testImplementation 'junit:junit:4.12'
|
||||||
|
implementation 'androidx.appcompat:appcompat:1.0.0'
|
||||||
|
}
|
||||||
34
gstreamer_player/jni/Android.mk
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
LOCAL_PATH := $(call my-dir)
|
||||||
|
|
||||||
|
include $(CLEAR_VARS)
|
||||||
|
|
||||||
|
LOCAL_MODULE := gst_player
|
||||||
|
LOCAL_SRC_FILES := gst_player.c dummy.cpp
|
||||||
|
LOCAL_SHARED_LIBRARIES := gstreamer_android
|
||||||
|
LOCAL_LDLIBS := -llog -landroid
|
||||||
|
include $(BUILD_SHARED_LIBRARY)
|
||||||
|
|
||||||
|
ifndef GSTREAMER_ROOT_ANDROID
|
||||||
|
$(error GSTREAMER_ROOT_ANDROID is not defined!)
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(TARGET_ARCH_ABI),armeabi)
|
||||||
|
GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/arm
|
||||||
|
else ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)
|
||||||
|
GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/armv7
|
||||||
|
else ifeq ($(TARGET_ARCH_ABI),arm64-v8a)
|
||||||
|
GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/arm64
|
||||||
|
else ifeq ($(TARGET_ARCH_ABI),x86)
|
||||||
|
GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/x86
|
||||||
|
else ifeq ($(TARGET_ARCH_ABI),x86_64)
|
||||||
|
GSTREAMER_ROOT := $(GSTREAMER_ROOT_ANDROID)/x86_64
|
||||||
|
else
|
||||||
|
$(error Target arch ABI not supported: $(TARGET_ARCH_ABI))
|
||||||
|
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)
|
||||||
|
G_IO_MODULES := openssl
|
||||||
|
GSTREAMER_EXTRA_DEPS := gstreamer-video-1.0
|
||||||
|
include $(GSTREAMER_NDK_BUILD_PATH)/gstreamer-1.0.mk
|
||||||
2
gstreamer_player/jni/Application.mk
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
APP_ABI = armeabi armeabi-v7a arm64-v8a x86 x86_64
|
||||||
|
APP_STL = c++_shared
|
||||||
0
gstreamer_player/jni/dummy.cpp
Normal file
629
gstreamer_player/jni/gst_player.c
Normal file
@ -0,0 +1,629 @@
|
|||||||
|
#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/gst.h>
|
||||||
|
#include <gst/video/video.h>
|
||||||
|
#include <gst/video/videooverlay.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
GST_DEBUG_CATEGORY_STATIC (debug_category);
|
||||||
|
#define GST_CAT_DEFAULT debug_category
|
||||||
|
|
||||||
|
/*
|
||||||
|
* These macros provide a way to store the native pointer to CustomData, 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) (CustomData *)(*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) (CustomData *)(jint)(*env)->GetLongField (env, thiz, fieldID)
|
||||||
|
# define SET_CUSTOM_DATA(env, thiz, fieldID, data) (*env)->SetLongField (env, thiz, fieldID, (jlong)(jint)data)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Do not allow seeks to be performed closer than this distance. It is visually useless, and will probably
|
||||||
|
* confuse some demuxers. */
|
||||||
|
#define SEEK_MIN_DELAY (500 * GST_MSECOND)
|
||||||
|
|
||||||
|
/* Structure to contain all our information, so we can pass it to callbacks */
|
||||||
|
typedef struct _CustomData
|
||||||
|
{
|
||||||
|
jobject app; /* Application instance, used to call its methods. A global reference is kept. */
|
||||||
|
GstElement *pipeline; /* The running pipeline */
|
||||||
|
GMainContext *context; /* GLib context used to run the main loop */
|
||||||
|
GMainLoop *main_loop; /* GLib main loop */
|
||||||
|
gboolean initialized; /* To avoid informing the UI multiple times about the initialization */
|
||||||
|
ANativeWindow *native_window; /* The Android native window where video will be rendered */
|
||||||
|
GstState state; /* Current pipeline state */
|
||||||
|
GstState target_state; /* Desired pipeline state, to be set once buffering is complete */
|
||||||
|
gint64 duration; /* Cached clip duration */
|
||||||
|
gint64 desired_position; /* Position to seek to, once the pipeline is running */
|
||||||
|
GstClockTime last_seek_time; /* For seeking overflow prevention (throttling) */
|
||||||
|
gboolean is_live; /* Live streams do not use buffering */
|
||||||
|
} CustomData;
|
||||||
|
|
||||||
|
/* playbin2 flags */
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
GST_PLAY_FLAG_TEXT = (1 << 2) /* We want subtitle output */
|
||||||
|
} GstPlayFlags;
|
||||||
|
|
||||||
|
/* These global variables cache values which are not changing during execution */
|
||||||
|
static pthread_t gst_app_thread;
|
||||||
|
static pthread_key_t current_jni_env;
|
||||||
|
static JavaVM *java_vm;
|
||||||
|
static jfieldID custom_data_field_id;
|
||||||
|
static jmethodID set_message_method_id;
|
||||||
|
static jmethodID set_current_position_method_id;
|
||||||
|
static jmethodID on_gstreamer_initialized_method_id;
|
||||||
|
static jmethodID on_media_size_changed_method_id;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Private methods
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Change the content of the UI's TextView */
|
||||||
|
static void set_ui_message (const gchar * message, CustomData * data) {
|
||||||
|
JNIEnv *env = get_jni_env ();
|
||||||
|
GST_DEBUG ("Setting message to: %s", message);
|
||||||
|
jstring jmessage = (*env)->NewStringUTF (env, message);
|
||||||
|
(*env)->CallVoidMethod (env, data->app, set_message_method_id, jmessage);
|
||||||
|
if ((*env)->ExceptionCheck (env)) {
|
||||||
|
GST_ERROR ("Failed to call Java method");
|
||||||
|
(*env)->ExceptionClear (env);
|
||||||
|
}
|
||||||
|
(*env)->DeleteLocalRef (env, jmessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tell the application what is the current position and clip duration */
|
||||||
|
static void set_current_ui_position (gint position, gint duration, CustomData * data) {
|
||||||
|
JNIEnv *env = get_jni_env ();
|
||||||
|
(*env)->CallVoidMethod (env, data->app, set_current_position_method_id,
|
||||||
|
position, duration);
|
||||||
|
if ((*env)->ExceptionCheck (env)) {
|
||||||
|
GST_ERROR ("Failed to call Java method");
|
||||||
|
(*env)->ExceptionClear (env);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we have pipeline and it is running, query the current position and clip duration and inform
|
||||||
|
* the application */
|
||||||
|
static gboolean refresh_ui (CustomData * data) {
|
||||||
|
gint64 current = -1;
|
||||||
|
gint64 position;
|
||||||
|
|
||||||
|
/* We do not want to update anything unless we have a working pipeline in the PAUSED or PLAYING state */
|
||||||
|
if (!data || !data->pipeline || data->state < GST_STATE_PAUSED)
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
/* If we didn't know it yet, query the stream duration */
|
||||||
|
if (!GST_CLOCK_TIME_IS_VALID (data->duration)) {
|
||||||
|
if (!gst_element_query_duration (data->pipeline, GST_FORMAT_TIME,
|
||||||
|
&data->duration)) {
|
||||||
|
GST_WARNING
|
||||||
|
("Could not query current duration (normal for still pictures)");
|
||||||
|
data->duration = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gst_element_query_position (data->pipeline, GST_FORMAT_TIME, &position)) {
|
||||||
|
GST_WARNING
|
||||||
|
("Could not query current position (normal for still pictures)");
|
||||||
|
position = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Java expects these values in milliseconds, and GStreamer provides nanoseconds */
|
||||||
|
set_current_ui_position (position / GST_MSECOND, data->duration / GST_MSECOND,
|
||||||
|
data);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Forward declaration for the delayed seek callback */
|
||||||
|
static gboolean delayed_seek_cb (CustomData * data);
|
||||||
|
|
||||||
|
/* Perform seek, if we are not too close to the previous seek. Otherwise, schedule the seek for
|
||||||
|
* some time in the future. */
|
||||||
|
static void execute_seek (gint64 desired_position, CustomData * data) {
|
||||||
|
gint64 diff;
|
||||||
|
|
||||||
|
if (desired_position == GST_CLOCK_TIME_NONE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
diff = gst_util_get_timestamp () - data->last_seek_time;
|
||||||
|
|
||||||
|
if (GST_CLOCK_TIME_IS_VALID (data->last_seek_time) && diff < SEEK_MIN_DELAY) {
|
||||||
|
/* The previous seek was too close, delay this one */
|
||||||
|
GSource *timeout_source;
|
||||||
|
|
||||||
|
if (data->desired_position == GST_CLOCK_TIME_NONE) {
|
||||||
|
/* There was no previous seek scheduled. Setup a timer for some time in the future */
|
||||||
|
timeout_source =
|
||||||
|
g_timeout_source_new ((SEEK_MIN_DELAY - diff) / GST_MSECOND);
|
||||||
|
g_source_set_callback (timeout_source, (GSourceFunc) delayed_seek_cb,
|
||||||
|
data, NULL);
|
||||||
|
g_source_attach (timeout_source, data->context);
|
||||||
|
g_source_unref (timeout_source);
|
||||||
|
}
|
||||||
|
/* Update the desired seek position. If multiple petitions are received before it is time
|
||||||
|
* to perform a seek, only the last one is remembered. */
|
||||||
|
data->desired_position = desired_position;
|
||||||
|
GST_DEBUG ("Throttling seek to %" GST_TIME_FORMAT ", will be in %"
|
||||||
|
GST_TIME_FORMAT, GST_TIME_ARGS (desired_position),
|
||||||
|
GST_TIME_ARGS (SEEK_MIN_DELAY - diff));
|
||||||
|
} else {
|
||||||
|
/* Perform the seek now */
|
||||||
|
GST_DEBUG ("Seeking to %" GST_TIME_FORMAT,
|
||||||
|
GST_TIME_ARGS (desired_position));
|
||||||
|
data->last_seek_time = gst_util_get_timestamp ();
|
||||||
|
gst_element_seek_simple (data->pipeline, GST_FORMAT_TIME,
|
||||||
|
GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, desired_position);
|
||||||
|
data->desired_position = GST_CLOCK_TIME_NONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Delayed seek callback. This gets called by the timer setup in the above function. */
|
||||||
|
static gboolean delayed_seek_cb (CustomData * data) {
|
||||||
|
GST_DEBUG ("Doing delayed seek to %" GST_TIME_FORMAT,
|
||||||
|
GST_TIME_ARGS (data->desired_position));
|
||||||
|
execute_seek (data->desired_position, data);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Retrieve errors from the bus and show them on the UI */
|
||||||
|
static void error_cb (GstBus * bus, GstMessage * msg, CustomData * data) {
|
||||||
|
GError *err;
|
||||||
|
gchar *debug_info;
|
||||||
|
gchar *message_string;
|
||||||
|
|
||||||
|
gst_message_parse_error (msg, &err, &debug_info);
|
||||||
|
message_string =
|
||||||
|
g_strdup_printf ("Error received from element %s: %s",
|
||||||
|
GST_OBJECT_NAME (msg->src), err->message);
|
||||||
|
g_clear_error (&err);
|
||||||
|
g_free (debug_info);
|
||||||
|
set_ui_message (message_string, data);
|
||||||
|
g_free (message_string);
|
||||||
|
data->target_state = GST_STATE_NULL;
|
||||||
|
gst_element_set_state (data->pipeline, GST_STATE_NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Called when the End Of the Stream is reached. Just move to the beginning of the media and pause. */
|
||||||
|
static void eos_cb (GstBus * bus, GstMessage * msg, CustomData * data) {
|
||||||
|
data->target_state = GST_STATE_PAUSED;
|
||||||
|
data->is_live |=
|
||||||
|
(gst_element_set_state (data->pipeline,
|
||||||
|
GST_STATE_PAUSED) == GST_STATE_CHANGE_NO_PREROLL);
|
||||||
|
execute_seek (0, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Called when the duration of the media changes. Just mark it as unknown, so we re-query it in the next UI refresh. */
|
||||||
|
static void duration_cb (GstBus * bus, GstMessage * msg, CustomData * data) {
|
||||||
|
data->duration = GST_CLOCK_TIME_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Called when buffering messages are received. We inform the UI about the current buffering level and
|
||||||
|
* keep the pipeline paused until 100% buffering is reached. At that point, set the desired state. */
|
||||||
|
static void buffering_cb (GstBus * bus, GstMessage * msg, CustomData * data) {
|
||||||
|
gint percent;
|
||||||
|
|
||||||
|
if (data->is_live)
|
||||||
|
return;
|
||||||
|
|
||||||
|
gst_message_parse_buffering (msg, &percent);
|
||||||
|
if (percent < 100 && data->target_state >= GST_STATE_PAUSED) {
|
||||||
|
gchar *message_string = g_strdup_printf ("Buffering %d%%", percent);
|
||||||
|
gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
|
||||||
|
set_ui_message (message_string, data);
|
||||||
|
g_free (message_string);
|
||||||
|
} else if (data->target_state >= GST_STATE_PLAYING) {
|
||||||
|
gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
|
||||||
|
} else if (data->target_state >= GST_STATE_PAUSED) {
|
||||||
|
set_ui_message ("Buffering complete", data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Called when the clock is lost */
|
||||||
|
static void clock_lost_cb (GstBus * bus, GstMessage * msg, CustomData * data) {
|
||||||
|
if (data->target_state >= GST_STATE_PLAYING) {
|
||||||
|
gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
|
||||||
|
gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Retrieve the video sink's Caps and tell the application about the media size */
|
||||||
|
static void check_media_size (CustomData * data) {
|
||||||
|
JNIEnv *env = get_jni_env ();
|
||||||
|
GstElement *video_sink;
|
||||||
|
GstPad *video_sink_pad;
|
||||||
|
GstCaps *caps;
|
||||||
|
GstVideoInfo info;
|
||||||
|
|
||||||
|
/* Retrieve the Caps at the entrance of the video sink */
|
||||||
|
g_object_get (data->pipeline, "video-sink", &video_sink, NULL);
|
||||||
|
video_sink_pad = gst_element_get_static_pad (video_sink, "sink");
|
||||||
|
caps = gst_pad_get_current_caps (video_sink_pad);
|
||||||
|
|
||||||
|
if (gst_video_info_from_caps (&info, caps)) {
|
||||||
|
info.width = info.width * info.par_n / info.par_d;
|
||||||
|
GST_DEBUG ("Media size is %dx%d, notifying application", info.width,
|
||||||
|
info.height);
|
||||||
|
|
||||||
|
(*env)->CallVoidMethod (env, data->app, on_media_size_changed_method_id,
|
||||||
|
(jint) info.width, (jint) info.height);
|
||||||
|
if ((*env)->ExceptionCheck (env)) {
|
||||||
|
GST_ERROR ("Failed to call Java method");
|
||||||
|
(*env)->ExceptionClear (env);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gst_caps_unref (caps);
|
||||||
|
gst_object_unref (video_sink_pad);
|
||||||
|
gst_object_unref (video_sink);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Notify UI about pipeline state changes */
|
||||||
|
static void state_changed_cb (GstBus * bus, GstMessage * msg, CustomData * data) {
|
||||||
|
GstState old_state, new_state, pending_state;
|
||||||
|
gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
|
||||||
|
/* Only pay attention to messages coming from the pipeline, not its children */
|
||||||
|
if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->pipeline)) {
|
||||||
|
data->state = new_state;
|
||||||
|
gchar *message = g_strdup_printf ("State changed to %s",
|
||||||
|
gst_element_state_get_name (new_state));
|
||||||
|
set_ui_message (message, data);
|
||||||
|
g_free (message);
|
||||||
|
|
||||||
|
if (new_state == GST_STATE_NULL || new_state == GST_STATE_READY)
|
||||||
|
data->is_live = FALSE;
|
||||||
|
|
||||||
|
/* The Ready to Paused state change is particularly interesting: */
|
||||||
|
if (old_state == GST_STATE_READY && new_state == GST_STATE_PAUSED) {
|
||||||
|
/* By now the sink already knows the media size */
|
||||||
|
check_media_size (data);
|
||||||
|
|
||||||
|
/* If there was a scheduled seek, perform it now that we have moved to the Paused state */
|
||||||
|
if (GST_CLOCK_TIME_IS_VALID (data->desired_position))
|
||||||
|
execute_seek (data->desired_position, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if all conditions are met to report GStreamer as initialized.
|
||||||
|
* These conditions will change depending on the application */
|
||||||
|
static void check_initialization_complete (CustomData * data) {
|
||||||
|
JNIEnv *env = get_jni_env ();
|
||||||
|
if (!data->initialized && data->native_window && data->main_loop) {
|
||||||
|
GST_DEBUG
|
||||||
|
("Initialization complete, notifying application. native_window:%p main_loop:%p",
|
||||||
|
data->native_window, data->main_loop);
|
||||||
|
|
||||||
|
/* The main loop is running and we received a native window, inform the sink about it */
|
||||||
|
gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->pipeline),
|
||||||
|
(guintptr) data->native_window);
|
||||||
|
|
||||||
|
(*env)->CallVoidMethod (env, data->app, on_gstreamer_initialized_method_id);
|
||||||
|
if ((*env)->ExceptionCheck (env)) {
|
||||||
|
GST_ERROR ("Failed to call Java method");
|
||||||
|
(*env)->ExceptionClear (env);
|
||||||
|
}
|
||||||
|
data->initialized = TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main method for the native code. This is executed on its own thread. */
|
||||||
|
static void *app_function (void *userdata) {
|
||||||
|
JavaVMAttachArgs args;
|
||||||
|
GstBus *bus;
|
||||||
|
CustomData *data = (CustomData *) userdata;
|
||||||
|
GSource *timeout_source;
|
||||||
|
GSource *bus_source;
|
||||||
|
GError *error = NULL;
|
||||||
|
guint flags;
|
||||||
|
|
||||||
|
GST_DEBUG ("Creating pipeline in CustomData at %p", data);
|
||||||
|
|
||||||
|
/* Create our own GLib Main Context and make it the default one */
|
||||||
|
data->context = g_main_context_new ();
|
||||||
|
g_main_context_push_thread_default (data->context);
|
||||||
|
|
||||||
|
/* Build pipeline */
|
||||||
|
data->pipeline = gst_parse_launch ("playbin", &error);
|
||||||
|
if (error) {
|
||||||
|
gchar *message =
|
||||||
|
g_strdup_printf ("Unable to build pipeline: %s", error->message);
|
||||||
|
g_clear_error (&error);
|
||||||
|
set_ui_message (message, data);
|
||||||
|
g_free (message);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Disable subtitles */
|
||||||
|
g_object_get (data->pipeline, "flags", &flags, NULL);
|
||||||
|
flags &= ~GST_PLAY_FLAG_TEXT;
|
||||||
|
g_object_set (data->pipeline, "flags", flags, NULL);
|
||||||
|
|
||||||
|
/* Set the pipeline to READY, so it can already accept a window handle, if we have one */
|
||||||
|
data->target_state = GST_STATE_READY;
|
||||||
|
gst_element_set_state (data->pipeline, GST_STATE_READY);
|
||||||
|
|
||||||
|
/* Instruct the bus to emit signals for each received message, and connect to the interesting signals */
|
||||||
|
bus = gst_element_get_bus (data->pipeline);
|
||||||
|
bus_source = gst_bus_create_watch (bus);
|
||||||
|
g_source_set_callback (bus_source, (GSourceFunc) gst_bus_async_signal_func,
|
||||||
|
NULL, NULL);
|
||||||
|
g_source_attach (bus_source, data->context);
|
||||||
|
g_source_unref (bus_source);
|
||||||
|
g_signal_connect (G_OBJECT (bus), "message::error", (GCallback) error_cb,
|
||||||
|
data);
|
||||||
|
g_signal_connect (G_OBJECT (bus), "message::eos", (GCallback) eos_cb, data);
|
||||||
|
g_signal_connect (G_OBJECT (bus), "message::state-changed",
|
||||||
|
(GCallback) state_changed_cb, data);
|
||||||
|
g_signal_connect (G_OBJECT (bus), "message::duration",
|
||||||
|
(GCallback) duration_cb, data);
|
||||||
|
g_signal_connect (G_OBJECT (bus), "message::buffering",
|
||||||
|
(GCallback) buffering_cb, data);
|
||||||
|
g_signal_connect (G_OBJECT (bus), "message::clock-lost",
|
||||||
|
(GCallback) clock_lost_cb, data);
|
||||||
|
gst_object_unref (bus);
|
||||||
|
|
||||||
|
/* Register a function that GLib will call 4 times per second */
|
||||||
|
timeout_source = g_timeout_source_new (250);
|
||||||
|
g_source_set_callback (timeout_source, (GSourceFunc) refresh_ui, data, NULL);
|
||||||
|
g_source_attach (timeout_source, data->context);
|
||||||
|
g_source_unref (timeout_source);
|
||||||
|
|
||||||
|
/* Create a GLib Main Loop and set it to run */
|
||||||
|
GST_DEBUG ("Entering main loop... (CustomData:%p)", data);
|
||||||
|
data->main_loop = g_main_loop_new (data->context, FALSE);
|
||||||
|
check_initialization_complete (data);
|
||||||
|
g_main_loop_run (data->main_loop);
|
||||||
|
GST_DEBUG ("Exited main loop");
|
||||||
|
g_main_loop_unref (data->main_loop);
|
||||||
|
data->main_loop = NULL;
|
||||||
|
|
||||||
|
/* Free resources */
|
||||||
|
g_main_context_pop_thread_default (data->context);
|
||||||
|
g_main_context_unref (data->context);
|
||||||
|
data->target_state = GST_STATE_NULL;
|
||||||
|
gst_element_set_state (data->pipeline, GST_STATE_NULL);
|
||||||
|
gst_object_unref (data->pipeline);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Java Bindings
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Instruct the native code to create its internal data structure, pipeline and thread */
|
||||||
|
static void gst_native_init (JNIEnv * env, jobject thiz) {
|
||||||
|
CustomData *data = g_new0 (CustomData, 1);
|
||||||
|
data->desired_position = GST_CLOCK_TIME_NONE;
|
||||||
|
data->last_seek_time = GST_CLOCK_TIME_NONE;
|
||||||
|
SET_CUSTOM_DATA (env, thiz, custom_data_field_id, data);
|
||||||
|
GST_DEBUG_CATEGORY_INIT (debug_category, "player", 0,
|
||||||
|
"Android tutorial 5");
|
||||||
|
gst_debug_set_threshold_for_name ("player", GST_LEVEL_DEBUG);
|
||||||
|
GST_DEBUG ("Created CustomData at %p", data);
|
||||||
|
data->app = (*env)->NewGlobalRef (env, thiz);
|
||||||
|
GST_DEBUG ("Created GlobalRef for app object at %p", data->app);
|
||||||
|
pthread_create (&gst_app_thread, NULL, &app_function, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Quit the main loop, remove the native thread and free resources */
|
||||||
|
static void gst_native_finalize (JNIEnv * env, jobject thiz) {
|
||||||
|
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
|
||||||
|
if (!data)
|
||||||
|
return;
|
||||||
|
GST_DEBUG ("Quitting main loop...");
|
||||||
|
g_main_loop_quit (data->main_loop);
|
||||||
|
GST_DEBUG ("Waiting for thread to finish...");
|
||||||
|
pthread_join (gst_app_thread, NULL);
|
||||||
|
GST_DEBUG ("Deleting GlobalRef for app object at %p", data->app);
|
||||||
|
(*env)->DeleteGlobalRef (env, data->app);
|
||||||
|
GST_DEBUG ("Freeing CustomData at %p", data);
|
||||||
|
g_free (data);
|
||||||
|
SET_CUSTOM_DATA (env, thiz, custom_data_field_id, NULL);
|
||||||
|
GST_DEBUG ("Done finalizing");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set playbin2's URI */
|
||||||
|
void gst_native_set_uri (JNIEnv * env, jobject thiz, jstring uri) {
|
||||||
|
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
|
||||||
|
if (!data || !data->pipeline)
|
||||||
|
return;
|
||||||
|
const gchar *char_uri = (*env)->GetStringUTFChars (env, uri, NULL);
|
||||||
|
GST_DEBUG ("Setting URI to %s", char_uri);
|
||||||
|
if (data->target_state >= GST_STATE_READY)
|
||||||
|
gst_element_set_state (data->pipeline, GST_STATE_READY);
|
||||||
|
g_object_set (data->pipeline, "uri", char_uri, NULL);
|
||||||
|
(*env)->ReleaseStringUTFChars (env, uri, char_uri);
|
||||||
|
data->duration = GST_CLOCK_TIME_NONE;
|
||||||
|
data->is_live |=
|
||||||
|
(gst_element_set_state (data->pipeline,
|
||||||
|
data->target_state) == GST_STATE_CHANGE_NO_PREROLL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set pipeline to PLAYING state */
|
||||||
|
static void gst_native_play (JNIEnv * env, jobject thiz) {
|
||||||
|
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
|
||||||
|
if (!data)
|
||||||
|
return;
|
||||||
|
GST_DEBUG ("Setting state to PLAYING");
|
||||||
|
data->target_state = GST_STATE_PLAYING;
|
||||||
|
data->is_live |=
|
||||||
|
(gst_element_set_state (data->pipeline,
|
||||||
|
GST_STATE_PLAYING) == GST_STATE_CHANGE_NO_PREROLL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set pipeline to PAUSED state */
|
||||||
|
static void gst_native_pause (JNIEnv * env, jobject thiz) {
|
||||||
|
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
|
||||||
|
if (!data)
|
||||||
|
return;
|
||||||
|
GST_DEBUG ("Setting state to PAUSED");
|
||||||
|
data->target_state = GST_STATE_PAUSED;
|
||||||
|
data->is_live |=
|
||||||
|
(gst_element_set_state (data->pipeline,
|
||||||
|
GST_STATE_PAUSED) == GST_STATE_CHANGE_NO_PREROLL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Instruct the pipeline to seek to a different position */
|
||||||
|
void gst_native_set_position (JNIEnv * env, jobject thiz, int milliseconds) {
|
||||||
|
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
|
||||||
|
if (!data)
|
||||||
|
return;
|
||||||
|
gint64 desired_position = (gint64) (milliseconds * GST_MSECOND);
|
||||||
|
if (data->state >= GST_STATE_PAUSED) {
|
||||||
|
execute_seek (desired_position, data);
|
||||||
|
} else {
|
||||||
|
GST_DEBUG ("Scheduling seek to %" GST_TIME_FORMAT " for later",
|
||||||
|
GST_TIME_ARGS (desired_position));
|
||||||
|
data->desired_position = desired_position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Static class initializer: retrieve method and field IDs */
|
||||||
|
static jboolean gst_native_class_init (JNIEnv * env, jclass klass) {
|
||||||
|
custom_data_field_id =
|
||||||
|
(*env)->GetFieldID (env, klass, "native_custom_data", "J");
|
||||||
|
set_message_method_id =
|
||||||
|
(*env)->GetMethodID (env, klass, "setMessage", "(Ljava/lang/String;)V");
|
||||||
|
set_current_position_method_id =
|
||||||
|
(*env)->GetMethodID (env, klass, "setCurrentPosition", "(II)V");
|
||||||
|
on_gstreamer_initialized_method_id =
|
||||||
|
(*env)->GetMethodID (env, klass, "onGStreamerInitialized", "()V");
|
||||||
|
on_media_size_changed_method_id =
|
||||||
|
(*env)->GetMethodID (env, klass, "onMediaSizeChanged", "(II)V");
|
||||||
|
|
||||||
|
if (!custom_data_field_id || !set_message_method_id
|
||||||
|
|| !on_gstreamer_initialized_method_id || !on_media_size_changed_method_id
|
||||||
|
|| !set_current_position_method_id) {
|
||||||
|
/* We emit this message through the Android log instead of the GStreamer log because the later
|
||||||
|
* has not been initialized yet.
|
||||||
|
*/
|
||||||
|
__android_log_print (ANDROID_LOG_ERROR, "tutorial-4",
|
||||||
|
"The calling class does not implement all necessary interface methods");
|
||||||
|
return JNI_FALSE;
|
||||||
|
}
|
||||||
|
return JNI_TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gst_native_surface_init (JNIEnv * env, jobject thiz, jobject surface) {
|
||||||
|
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
|
||||||
|
if (!data)
|
||||||
|
return;
|
||||||
|
ANativeWindow *new_native_window = ANativeWindow_fromSurface (env, surface);
|
||||||
|
GST_DEBUG ("Received surface %p (native window %p)", surface,
|
||||||
|
new_native_window);
|
||||||
|
|
||||||
|
if (data->native_window) {
|
||||||
|
ANativeWindow_release (data->native_window);
|
||||||
|
if (data->native_window == new_native_window) {
|
||||||
|
GST_DEBUG ("New native window is the same as the previous one %p",
|
||||||
|
data->native_window);
|
||||||
|
if (data->pipeline) {
|
||||||
|
gst_video_overlay_expose (GST_VIDEO_OVERLAY (data->pipeline));
|
||||||
|
gst_video_overlay_expose (GST_VIDEO_OVERLAY (data->pipeline));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
GST_DEBUG ("Released previous native window %p", data->native_window);
|
||||||
|
data->initialized = FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data->native_window = new_native_window;
|
||||||
|
|
||||||
|
check_initialization_complete (data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gst_native_surface_finalize (JNIEnv * env, jobject thiz) {
|
||||||
|
CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id);
|
||||||
|
if (!data)
|
||||||
|
return;
|
||||||
|
GST_DEBUG ("Releasing Native Window %p", data->native_window);
|
||||||
|
|
||||||
|
if (data->pipeline) {
|
||||||
|
gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->pipeline),
|
||||||
|
(guintptr) NULL);
|
||||||
|
gst_element_set_state (data->pipeline, GST_STATE_READY);
|
||||||
|
}
|
||||||
|
|
||||||
|
ANativeWindow_release (data->native_window);
|
||||||
|
data->native_window = NULL;
|
||||||
|
data->initialized = FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* List of implemented native methods */
|
||||||
|
static JNINativeMethod native_methods[] = {
|
||||||
|
{"nativeInit", "()V", (void *) gst_native_init},
|
||||||
|
{"nativeFinalize", "()V", (void *) gst_native_finalize},
|
||||||
|
{"nativeSetUri", "(Ljava/lang/String;)V", (void *) gst_native_set_uri},
|
||||||
|
{"nativePlay", "()V", (void *) gst_native_play},
|
||||||
|
{"nativePause", "()V", (void *) gst_native_pause},
|
||||||
|
{"nativeSetPosition", "(I)V", (void *) gst_native_set_position},
|
||||||
|
{"nativeSurfaceInit", "(Ljava/lang/Object;)V",
|
||||||
|
(void *) gst_native_surface_init},
|
||||||
|
{"nativeSurfaceFinalize", "()V", (void *) gst_native_surface_finalize},
|
||||||
|
{"nativeClassInit", "()Z", (void *) gst_native_class_init}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* 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, "player",
|
||||||
|
"Could not retrieve JNIEnv");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
jclass klass = (*env)->FindClass (env,
|
||||||
|
"com/hisharp/gstreamer_player/GstLibrary");
|
||||||
|
(*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;
|
||||||
|
}
|
||||||
BIN
gstreamer_player/res/drawable-ldpi/file.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
gstreamer_player/res/drawable-ldpi/folder.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
gstreamer_player/res/drawable-ldpi/gstreamer_logo_5.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
gstreamer_player/res/drawable-mdpi/gstreamer_logo_5.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
gstreamer_player/res/drawable-xhdpi/gstreamer_logo_5.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
gstreamer_player/res/drawable-xxhdpi/gstreamer_logo_5.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
gstreamer_player/res/drawable-xxxhdpi/gstreamer_logo_5.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
46
gstreamer_player/res/layout/main.xml
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?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" >
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textview_message"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="16dip"
|
||||||
|
android:gravity="center_horizontal" />
|
||||||
|
|
||||||
|
<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_stop"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@string/button_stop"
|
||||||
|
android:src="@android:drawable/ic_media_pause"
|
||||||
|
android:text="@string/button_stop" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<com.hisharp.gstreamer_player.GStreamerSurfaceView
|
||||||
|
android:id="@+id/surface_video"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical|center_horizontal" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
12
gstreamer_player/res/values/strings.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">GStreamer tutorial 5</string>
|
||||||
|
<string name="button_play">Play</string>
|
||||||
|
<string name="button_stop">Stop</string>
|
||||||
|
<string name="button_select">Select</string>
|
||||||
|
<string name="button_cancel">Cancel</string>
|
||||||
|
<string name="filechooser_name">Select a file</string>
|
||||||
|
<string name="location">Location</string>
|
||||||
|
<string name="cant_read_folder">folder cannot be read</string>
|
||||||
|
<string name="icon">Icon</string>
|
||||||
|
</resources>
|
||||||
@ -0,0 +1,84 @@
|
|||||||
|
package com.hisharp.gstreamer_player;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.SurfaceView;
|
||||||
|
|
||||||
|
// 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 = MeasureSpec.getMode(widthMeasureSpec);
|
||||||
|
int hmode = MeasureSpec.getMode(heightMeasureSpec);
|
||||||
|
int wsize = MeasureSpec.getSize(widthMeasureSpec);
|
||||||
|
int hsize = MeasureSpec.getSize(heightMeasureSpec);
|
||||||
|
|
||||||
|
Log.i ("GStreamer", "onMeasure called with " + media_width + "x" + media_height);
|
||||||
|
// Obey width rules
|
||||||
|
switch (wmode) {
|
||||||
|
case MeasureSpec.AT_MOST:
|
||||||
|
if (hmode == MeasureSpec.EXACTLY) {
|
||||||
|
width = Math.min(hsize * media_width / media_height, wsize);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MeasureSpec.EXACTLY:
|
||||||
|
width = wsize;
|
||||||
|
break;
|
||||||
|
case MeasureSpec.UNSPECIFIED:
|
||||||
|
width = media_width;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obey height rules
|
||||||
|
switch (hmode) {
|
||||||
|
case MeasureSpec.AT_MOST:
|
||||||
|
if (wmode == MeasureSpec.EXACTLY) {
|
||||||
|
height = Math.min(wsize * media_height / media_width, hsize);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MeasureSpec.EXACTLY:
|
||||||
|
height = hsize;
|
||||||
|
break;
|
||||||
|
case MeasureSpec.UNSPECIFIED:
|
||||||
|
height = media_height;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, calculate best size when both axis are free
|
||||||
|
if (hmode == MeasureSpec.AT_MOST && wmode == 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,7 @@
|
|||||||
|
package com.hisharp.gstreamer_player;
|
||||||
|
|
||||||
|
public interface GstCallback {
|
||||||
|
public void onStatus(GstStatus gstStatus);
|
||||||
|
public void onMessage(String message);
|
||||||
|
public void onMediaSizeChanged (int width, int height);
|
||||||
|
}
|
||||||
@ -0,0 +1,137 @@
|
|||||||
|
package com.hisharp.gstreamer_player;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.Surface;
|
||||||
|
import android.view.SurfaceHolder;
|
||||||
|
import android.view.SurfaceView;
|
||||||
|
|
||||||
|
import org.freedesktop.gstreamer.GStreamer;
|
||||||
|
|
||||||
|
public class GstLibrary implements SurfaceHolder.Callback {
|
||||||
|
|
||||||
|
final Context mAppContext;
|
||||||
|
|
||||||
|
private GstCallback gstCallback;
|
||||||
|
|
||||||
|
private GStreamerSurfaceView surfaceView;
|
||||||
|
|
||||||
|
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||||
|
|
||||||
|
private final String rtspUrl;
|
||||||
|
|
||||||
|
public GstLibrary(Context context, String rtspUrl) {
|
||||||
|
this.mAppContext = context.getApplicationContext();
|
||||||
|
this.rtspUrl = rtspUrl;
|
||||||
|
|
||||||
|
// Initialize GStreamer and warn if it fails
|
||||||
|
try {
|
||||||
|
GStreamer.init(mAppContext);
|
||||||
|
} catch (Exception e) {
|
||||||
|
new Exception("Unable initialize gstreamer.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nativeInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
private native void nativeInit(); // Initialize native code, build pipeline, etc
|
||||||
|
private native void nativeFinalize(); // Destroy pipeline and shutdown native code
|
||||||
|
private native void nativeSetUri(String uri); // Set the URI of the media to play
|
||||||
|
private native void nativePlay(); // Set pipeline to PLAYING
|
||||||
|
private native void nativeSetPosition(int milliseconds); // Seek to the indicated position, in milliseconds
|
||||||
|
private native void nativePause(); // Set pipeline to PAUSED
|
||||||
|
private static native boolean nativeClassInit(); // Initialize native class: cache Method IDs for callbacks
|
||||||
|
private native void nativeSurfaceInit(Object surface); // A new surface is available
|
||||||
|
private native void nativeSurfaceFinalize(); // Surface about to be destroyed
|
||||||
|
private long native_custom_data; // Native code will use this to keep private data
|
||||||
|
|
||||||
|
//private final String defaultMediaUri = "rtsp://admin:admin@192.168.0.77:554/media/video2";
|
||||||
|
|
||||||
|
public void play() {
|
||||||
|
nativePlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
nativePause();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void release() {
|
||||||
|
nativeFinalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnStatusChangeListener(GstCallback callback) {
|
||||||
|
this.gstCallback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSurfaceView(GStreamerSurfaceView surfaceView) {
|
||||||
|
this.surfaceView = surfaceView;
|
||||||
|
SurfaceHolder holder = this.surfaceView.getHolder();
|
||||||
|
holder.addCallback(this);
|
||||||
|
nativeSurfaceInit(holder.getSurface());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called from native code. This sets the content of the TextView from the UI thread.
|
||||||
|
private void setMessage(final String message) {
|
||||||
|
if (gstCallback == null) return;
|
||||||
|
if (message.contains("State changed to PAUSED")) {
|
||||||
|
gstCallback.onStatus(GstStatus.PAUSE);
|
||||||
|
} else if (message.contains("State changed to PLAYING")) {
|
||||||
|
gstCallback.onStatus(GstStatus.PLAYING);
|
||||||
|
}
|
||||||
|
gstCallback.onMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called from native code. Native code calls this once it has created its pipeline and
|
||||||
|
// the main loop is running, so it is ready to accept commands.
|
||||||
|
private void onGStreamerInitialized () {
|
||||||
|
Log.i ("GStreamer", "GStreamer initialized:");
|
||||||
|
|
||||||
|
if (gstCallback != null) {
|
||||||
|
gstCallback.onStatus(GstStatus.READY);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore previous playing state
|
||||||
|
nativeSetUri (rtspUrl);
|
||||||
|
nativeSetPosition (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called from native code
|
||||||
|
private void setCurrentPosition(final int position, final int duration) {}
|
||||||
|
|
||||||
|
static {
|
||||||
|
System.loadLibrary("gstreamer_android");
|
||||||
|
System.loadLibrary("gst_player");
|
||||||
|
nativeClassInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void surfaceChanged(SurfaceHolder holder, int format, int width,
|
||||||
|
int height) {
|
||||||
|
Log.d("GStreamer", "Surface changed to format " + format + " width "
|
||||||
|
+ width + " height " + height);
|
||||||
|
nativeSurfaceInit (holder.getSurface());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void surfaceCreated(SurfaceHolder holder) {
|
||||||
|
Log.d("GStreamer", "Surface created: " + holder.getSurface());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||||
|
Log.d("GStreamer", "Surface destroyed");
|
||||||
|
nativeSurfaceFinalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called from native code when the size of the media changes or is first detected.
|
||||||
|
// Inform the video surface about the new size and recalculate the layout.
|
||||||
|
private void onMediaSizeChanged (int width, int height) {
|
||||||
|
Log.i ("GStreamer", "Media size changed to " + width + "x" + height);
|
||||||
|
mainHandler.post(() -> {
|
||||||
|
surfaceView.media_width = width;
|
||||||
|
surfaceView.media_height = height;
|
||||||
|
surfaceView.requestLayout();
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
package com.hisharp.gstreamer_player;
|
||||||
|
|
||||||
|
public enum GstStatus {
|
||||||
|
READY,
|
||||||
|
PAUSE,
|
||||||
|
PLAYING
|
||||||
|
}
|
||||||
8
local.properties
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
## This file must *NOT* be checked into Version Control Systems,
|
||||||
|
# as it contains information specific to your local configuration.
|
||||||
|
#
|
||||||
|
# Location of the SDK. This is only used by Gradle.
|
||||||
|
# For customization when using a Version Control System, please read the
|
||||||
|
# header note.
|
||||||
|
#Fri Jul 29 13:50:29 CST 2022
|
||||||
|
sdk.dir=C\:\\Users\\hisharp\\AppData\\Local\\Android\\Sdk
|
||||||
2
settings.gradle
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
include ':gstreamer_player'
|
||||||
|
include ':app'
|
||||||