diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
new file mode 100644
index 0000000..c224ad5
--- /dev/null
+++ b/.idea/kotlinc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index f988785..0bd3ec2 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,4 +1,3 @@
-
diff --git a/app/build.gradle b/app/build.gradle
deleted file mode 100644
index a77c7ba..0000000
--- a/app/build.gradle
+++ /dev/null
@@ -1,53 +0,0 @@
-plugins {
- id 'com.android.application'
-}
-
-android {
- compileSdk 35
- buildToolsVersion '35.0.0'
-
- defaultConfig {
- applicationId "com.mediadevkit.mdkplayer"
- minSdkVersion 21
- targetSdkVersion 35
- versionCode 1
- versionName "1.0"
-
- //testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- }
-
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
- jniDebuggable true
- debuggable true
- }
- }
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_11
- targetCompatibility JavaVersion.VERSION_11
- }
- dependenciesInfo {
- includeInApk true
- }
- ndkVersion '27.2.12479018'
- // Extract native libraries from the APK
- packagingOptions.jniLibs.useLegacyPackaging true
- namespace 'com.mediadevkit.mdkplayer'
-}
-
-dependencies {
-
- implementation 'androidx.appcompat:appcompat:1.6.1'
- implementation 'com.google.android.material:material:1.8.0'
- implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
- //androidTestImplementation 'androidx.test.ext:junit:1.1.5'
- //androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
- // https://stackoverflow.com/questions/16682847/how-to-manually-include-external-aar-package-using-new-gradle-android-build-syst
- //implementation(name:'sdk-debug', ext:'aar')
- //implementation 'com.mediadevkit.sdk:sdk-debug@aar'
- //implementation files('../sdk/build/outputs/aar/sdk-debug.aar')
-// https://developer.android.com/studio/projects/android-library#CreateLibrary
- implementation project(":sdk")
-}
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
new file mode 100644
index 0000000..246ee11
--- /dev/null
+++ b/app/build.gradle.kts
@@ -0,0 +1,42 @@
+plugins {
+ alias(libs.plugins.androidApplication)
+ alias(libs.plugins.composeCompiler)
+ alias(libs.plugins.kotlin.android)
+}
+
+android {
+ namespace = "com.mediadevkit.mdkplayer"
+ compileSdk = libs.versions.compileSdk.get().toInt()
+
+ defaultConfig {
+ applicationId = "com.mediadevkit.mdkplayer"
+ minSdk = libs.versions.minSdk.get().toInt()
+ targetSdk = libs.versions.targetSdk.get().toInt()
+ versionCode = 1
+ versionName = "1.0"
+ }
+
+ buildTypes {
+ val release by getting {
+ isMinifyEnabled = true
+ isJniDebuggable = true
+ isShrinkResources = true
+ proguardFiles += getDefaultProguardFile("proguard-android-optimize.txt")
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+ kotlinOptions.jvmTarget = "11"
+ composeOptions.kotlinCompilerExtensionVersion = "1.5.14"
+}
+
+dependencies {
+ implementation(libs.androidx.activityCompose)
+ implementation(libs.androidx.appcompat)
+ implementation(libs.androidx.lifecycle)
+ implementation(projects.sdk)
+ implementation(libs.bundles.compose)
+}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 4607f6e..af5a272 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -4,17 +4,13 @@
+ android:theme="@style/Theme.Mdk"
+ android:icon="@mipmap/ic_launcher">
+
+ android:exported="true">
diff --git a/app/src/main/assets/fonts/font.ttf b/app/src/main/assets/fonts/font.ttf
new file mode 100644
index 0000000..9659afc
Binary files /dev/null and b/app/src/main/assets/fonts/font.ttf differ
diff --git a/app/src/main/java/com/mediadevkit/mdkplayer/MainActivity.java b/app/src/main/java/com/mediadevkit/mdkplayer/MainActivity.java
deleted file mode 100644
index 18fd60e..0000000
--- a/app/src/main/java/com/mediadevkit/mdkplayer/MainActivity.java
+++ /dev/null
@@ -1,225 +0,0 @@
-package com.mediadevkit.mdkplayer;
-
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.net.Uri;
-import android.opengl.GLSurfaceView;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Environment;
-import android.util.Log;
-import android.view.Display;
-import android.view.GestureDetector;
-import android.view.MotionEvent;
-import android.view.SurfaceView;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.Window;
-import android.view.WindowManager;
-import android.widget.Button;
-import android.widget.CompoundButton;
-import android.widget.LinearLayout;
-
-import androidx.activity.result.ActivityResultCallback;
-import androidx.activity.result.ActivityResultLauncher;
-import androidx.activity.result.contract.ActivityResultContracts;
-import androidx.annotation.NonNull;
-import androidx.appcompat.app.AppCompatActivity;
-
-import com.mediadevkit.sdk.MDKPlayer;
-
-import javax.microedition.khronos.egl.EGLConfig;
-import javax.microedition.khronos.opengles.GL10;
-import android.graphics.ColorSpace;
-import android.widget.Switch;
-
-// TODO: render to SurfaceTexture surface. OnFrameAvailable will be called after swapBuffers or data copied? http://blog.csdn.net/king1425/article/details/72773331
-// TODO: request permissions(sdcard) for android 23+
-// AppCompatActivity: incompatible with android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen" // https://stackoverflow.com/questions/39604889/how-to-fix-you-need-to-use-a-theme-appcompat-theme-or-descendant-with-this-a/39604946
-public class MainActivity extends AppCompatActivity {
- private SurfaceView mView = null;
- private GLSurfaceView mGLView = null;
- private MDKPlayer mPlayer = null;
- private VelocityTracker mVelocityTracker = null;
- private int mState = 0;
- private int mPos = 0;
- private float mX = 0;
- final GestureDetector gestureDetector = new GestureDetector(new GestureDetector.SimpleOnGestureListener());
- private ActivityResultLauncher mGetContent = registerForActivityResult(new ActivityResultContracts.GetContent(),
- new ActivityResultCallback() {
- @Override
- public void onActivityResult(Uri uri) {
- // Handle the returned Uri
- if (uri == null)
- return;
- Log.i("MDK.java", "mGetContent: " + uri.toString());
- mPlayer.setState(0);
- mPlayer.setNextMedia(null);
- mPlayer.setMedia(uri.toString());
- mPlayer.setState(1);
- }
- });
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- //getWindow().setColorMode(ActivityInfo.COLOR_MODE_HDR);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- Log.i("MDK.Java","getColorMode(): " + getWindow().getColorMode());
- }
- //this.requestWindowFeature(Window.FEATURE_NO_TITLE); //It's enough to remove the line
- //But if you want to display full screen (without action bar) write too
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
- setContentView(R.layout.activity_main);
- mPlayer = new MDKPlayer();
- mView = findViewById(R.id.surfaceView);
- mPlayer.setSurfaceView(mView);
- findViewById(R.id.Open).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- mGetContent.launch("video/*");
- }
- });
- Button playStopBtn = findViewById(R.id.PlayStop);
- playStopBtn.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- playStopBtn.setText(mPlayer.state() == 0 ? "Play" : "Stop");
- mPlayer.setState(mPlayer.state() == 0 ? 1 : 0);
- }
- });
- Switch hdr = findViewById(R.id.HDR);
- hdr.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(CompoundButton btn, boolean isChecked){
- if (isChecked)
- mPlayer.setColorSpace(0);
- else
- mPlayer.setColorSpace(1);
- }
- });
- hdr.setChecked(true);
-/*
- mGLView = findViewById(R.id.glSurfaceView);
- mGLView.setEGLConfigChooser(8, 8, 8, 0, 0, 0);
- mGLView.setEGLContextClientVersion(2);
- mGLView.setRenderer(new DemoRenderer(mPlayer));
-*/
- Intent intent = getIntent();
- String action = intent.getAction();
- if (Intent.ACTION_VIEW.equals(action)) {
- mPlayer.setMedia(intent.getDataString());
- } else {
- // getExternalFilesDir(Environment.DIRECTORY_MOVIES).toString() // app local
- //mPlayer.setMedia(Environment.getExternalStorageDirectory().toString() + "/Movies/Samsung Chasing The Light Demo.ts");
- //mPlayer.setMedia("https://live.nodemedia.cn:8443/live/b480_265.flv");
- //mPlayer.setMedia("http://192.168.3.168:8888/86831_2158.ts");
- //mPlayer.setMedia("https://www.rmp-streaming.com/media/big-buck-bunny-720p.mp4");
- String[] urls = new String[15];
- for (int i = 0; i < 10; ++i)
- urls[i] = "/sdcard/Movies/s/s0" + i + ".mkv";
- for (int i = 10; i < 15; ++i)
- urls[i] = "/sdcard/Movies/s/s" + i + ".mkv";
- //mPlayer.setPlayList(urls);
- }
- //setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
-
- gestureDetector.setOnDoubleTapListener(new GestureDetector.OnDoubleTapListener(){
- public boolean onDoubleTap(MotionEvent e) {
- //Log.i("MDK.Java","onDoubleTap e.getX(): " + e.getX());
- if (mPlayer.state() == 1)
- mPlayer.setState(2);
- else
- mPlayer.setState(1);
- return true;
- }
- public boolean onDoubleTapEvent(@NonNull MotionEvent e) { return false;}
- public boolean onSingleTapConfirmed(MotionEvent e) {return false;}
- });
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- //mView.setVisibility(View.GONE);
- mPlayer.setState(2);
- // if not set null here, surface holder will be destroyed too, but SurfaceHolder store in player class will not change when calling mPlayer.setSurfaceView(mView) in onResume() and surface is not updated in native
- mPlayer.setSurfaceView(null);
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- //mView.setVisibility(View.VISIBLE);
- mPlayer.setState(1);
- mPlayer.setSurfaceView(mView); // ensure vo is created
- }
-
- @Override
- public boolean onTouchEvent(final MotionEvent event) {
- gestureDetector.onTouchEvent(event);
- int action = event.getActionMasked();
- switch (action) {
- case MotionEvent.ACTION_DOWN: {
- mX = event.getX();
- mPos = mPlayer.position();
- mState = mPlayer.state();
- if (mState == 0)
- mState = 1;
- //Log.i("MDK.Java","DOWN event.getX(): " + event.getX() + " mPos:" + mPos);
- //mPlayer.setState(2);
- if (mState == 1)
- mPlayer.setState(2);
- else
- mPlayer.setState(1);
- if (mVelocityTracker == null)
- mVelocityTracker = VelocityTracker.obtain();
- mVelocityTracker.clear();
- mVelocityTracker.addMovement(event);
- }
- break;
- case MotionEvent.ACTION_MOVE: {
- mVelocityTracker.addMovement(event);
- mVelocityTracker.computeCurrentVelocity(1000);
- float dx = event.getX() - mX;
- if (dx > 20) { // TODO: depends on duration and screen size, not velocity
- mPos += 1000;
- mX = event.getX();
- } else if (dx < -20) {
- mPos -= 1000;
- mX = event.getX();
- }
- //Log.i("MDK.Java","MOVE event.getX(): " + event.getX() + " mPos:" + mPos);
- if (mPos > 0)
- mPlayer.seek(mPos);
- //Log.i("MDK.Java","velocityTraker: "+mVelocityTracker.getXVelocity());
- }
- break;
- case MotionEvent.ACTION_UP:
- //mPlayer.setState(mState);
- //Log.i("MDK.Java","UP event.getX(): " + event.getX());
- break;
- case MotionEvent.ACTION_CANCEL:
- mVelocityTracker.recycle();
- break;
- default:
- break;
- }
- return true;
- }
- static class DemoRenderer implements GLSurfaceView.Renderer {
- private final MDKPlayer mPlayer;
- DemoRenderer(MDKPlayer player) {
- mPlayer = player;
- }
- public void onSurfaceCreated(GL10 gl, EGLConfig config) {
- }
- public void onSurfaceChanged(GL10 gl, int w, int h) {
- mPlayer.resizeVideoSurface(w, h);
- }
- public void onDrawFrame(GL10 gl) {
- mPlayer.renderVideo();
- }
- }
-}
diff --git a/app/src/main/java/com/mediadevkit/mdkplayer/MainActivity.kt b/app/src/main/java/com/mediadevkit/mdkplayer/MainActivity.kt
new file mode 100644
index 0000000..d32cef9
--- /dev/null
+++ b/app/src/main/java/com/mediadevkit/mdkplayer/MainActivity.kt
@@ -0,0 +1,56 @@
+package com.mediadevkit.mdkplayer
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.*
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.ui.Modifier
+import com.mediadevkit.sdk.KotlinPlayer
+
+@Stable
+object Navigator {
+ var currentScreen: Screen by mutableStateOf(Screen.Root)
+}
+
+@Immutable
+sealed interface Screen {
+ data object Root : Screen
+ data class Player(val url: String, val config: KotlinPlayer.Config) : Screen
+}
+
+
+class MainActivity : ComponentActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent { App() }
+ }
+
+}
+
+@Composable
+fun App() {
+ MaterialTheme(
+ colorScheme = darkColorScheme(),
+ content = {
+ Scaffold(
+ content = {
+ BackHandler(
+ enabled = Navigator.currentScreen != Screen.Root,
+ onBack = { Navigator.currentScreen = Screen.Root },
+ )
+ val modifier = Modifier.padding(it)
+ when (val screen = Navigator.currentScreen) {
+ is Screen.Player -> PlayerScreen(modifier, screen.url, screen.config)
+ Screen.Root -> RootScreen(modifier)
+ }
+ }
+ )
+ }
+ )
+}
+
+
+
diff --git a/app/src/main/java/com/mediadevkit/mdkplayer/PlayerScreen.kt b/app/src/main/java/com/mediadevkit/mdkplayer/PlayerScreen.kt
new file mode 100644
index 0000000..6581c71
--- /dev/null
+++ b/app/src/main/java/com/mediadevkit/mdkplayer/PlayerScreen.kt
@@ -0,0 +1,323 @@
+package com.mediadevkit.mdkplayer
+
+import android.content.pm.ActivityInfo
+import android.os.Build
+import android.view.SurfaceView
+import androidx.activity.compose.LocalActivity
+import androidx.compose.foundation.*
+import androidx.compose.foundation.interaction.*
+import androidx.compose.foundation.layout.*
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.*
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.ui.*
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.lifecycle.*
+import androidx.lifecycle.compose.LocalLifecycleOwner
+import com.mediadevkit.sdk.*
+import kotlinx.coroutines.*
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
+
+@Composable
+fun PlayerScreen(
+ modifier: Modifier = Modifier,
+ url: String,
+ config: KotlinPlayer.Config,
+) {
+ val activity = LocalActivity.current
+ val player = remember { KotlinPlayer(config) }
+ val state = rememberPlayerState(player)
+ var userValue by remember { mutableFloatStateOf(0f) }
+ val sliderSource = remember(::MutableInteractionSource)
+ val isSliding by sliderSource.collectIsDraggedAsState()
+ var type by remember { mutableStateOf(MediaType.Unknown) }
+ val lifecycleOwner = LocalLifecycleOwner.current
+
+ val sliderValue by remember {
+ derivedStateOf {
+ when {
+ isSliding -> userValue
+ else -> state.progress
+ }
+ }
+ }
+
+ LaunchedEffect(
+ key1 = isSliding,
+ block = {
+ player.state = if (isSliding) 2 else 1
+ }
+ )
+ LaunchedEffect(
+ key1 = userValue,
+ block = { player.position = (state.duration * userValue.toDouble()).inWholeMilliseconds }
+ )
+ DisposableEffect(
+ key1 = Unit,
+ effect = {
+ player.state = 0
+ player.media = url
+ player.state = 1
+ val listener = LifecycleEventObserver { _, event ->
+ if (event.targetState == Lifecycle.State.CREATED) player.state = 2
+ }
+ lifecycleOwner.lifecycle.addObserver(listener)
+ onDispose {
+ lifecycleOwner.lifecycle.removeObserver(listener)
+ player.release()
+ }
+ }
+ )
+
+ TracksDialog(
+ type = type,
+ onClick = { index, stream ->
+ val key = when (stream) {
+ is MdkAudioStream -> "audio.tracks"
+ is MdkSubtitle -> "subtitle.tracks"
+ is MdkVideoStream -> "video.tracks"
+ }
+ player.setProperty(key, "-1") //fixme: if not set to -1 before, tracks are combined !!
+ player.setProperty(key, "$index")
+ when (stream) {
+ is MdkAudioStream -> state.audioTrack = index
+ is MdkSubtitle -> state.subtitleTrack = index
+ is MdkVideoStream -> state.videoTrack = index
+ }
+ type = MediaType.Unknown
+ },
+ onDismiss = { type = MediaType.Unknown },
+ state = state,
+ )
+
+ Column(
+ modifier = modifier.fillMaxSize(),
+ content = {
+ AndroidView(
+ modifier = Modifier.weight(1f),
+ factory = { SurfaceView(it).apply(player::setSurface) },
+ onRelease = { player.setSurface(null) },
+ )
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp),
+ content = {
+ IconButton(
+ onClick = {
+ when (state.state) {
+ 1 -> player.state = 2
+ else -> player.state = 1
+ }
+ },
+ content = {
+ Icon(
+ imageVector = when (state.state) {
+ 1 -> Icons.Default.Pause
+ 2 -> Icons.Default.PlayArrow
+ else -> Icons.Default.PlayArrow
+ },
+ contentDescription = null
+ )
+ }
+ )
+ Spacer(
+ modifier = Modifier.width(12.dp),
+ )
+ Slider(
+ modifier = modifier.weight(1f),
+ interactionSource = sliderSource,
+ value = sliderValue,
+ onValueChange = { userValue = it },
+ )
+ }
+ )
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp),
+ horizontalArrangement = Arrangement.spacedBy(12.dp, alignment = Alignment.CenterHorizontally),
+ content = {
+ IconButton(
+ onClick = {
+ state.hdr = !state.hdr
+ player.hdr = state.hdr
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ activity?.window?.colorMode = when {
+ state.hdr -> ActivityInfo.COLOR_MODE_HDR
+ else -> ActivityInfo.COLOR_MODE_DEFAULT
+ }
+ }
+ },
+ content = {
+ Icon(
+ imageVector = when {
+ state.hdr -> Icons.Default.HdrOn
+ else -> Icons.Default.HdrOff
+ },
+ contentDescription = null,
+ )
+ }
+ )
+ IconButton(
+ enabled = state.mediaInfo?.audio?.isNotEmpty() == true,
+ onClick = { type = MediaType.Audio },
+ content = { Icon(Icons.Default.Audiotrack, null) }
+ )
+ IconButton(
+ enabled = state.mediaInfo?.subtitles?.isNotEmpty() == true,
+ onClick = { type = MediaType.Subtitles },
+ content = { Icon(Icons.Default.ClosedCaption, null) }
+ )
+ }
+ )
+ },
+ )
+
+}
+
+@Stable
+class PlayerState {
+ var hdr: Boolean by mutableStateOf(false)
+ var position: Duration by mutableStateOf(Duration.ZERO)
+ var state: Int by mutableIntStateOf(0)
+ var mediaInfo: MdkMediaInfo? by mutableStateOf(null)
+ val duration: Duration by derivedStateOf { mediaInfo?.duration?.milliseconds ?: Duration.ZERO }
+
+ val progress: Float by derivedStateOf {
+ when {
+ duration <= Duration.ZERO -> 0f
+ else -> (position / duration).toFloat()
+ }
+ }
+
+ var audioTrack: Int by mutableIntStateOf(-1)
+ var videoTrack: Int by mutableIntStateOf(-1)
+ var subtitleTrack: Int by mutableIntStateOf(-1)
+
+}
+
+@Stable
+@Composable
+fun rememberPlayerState(player: KotlinPlayer): PlayerState {
+ val state = remember(::PlayerState)
+ LaunchedEffect(
+ key1 = Unit,
+ block = {
+ withContext(Dispatchers.IO) {
+ while (isActive) {
+ try {
+ withContext(Dispatchers.Main) {
+ state.position = player.position.milliseconds
+ }
+ } finally {
+ delay(100)
+ }
+ }
+ }
+ },
+ )
+ DisposableEffect(
+ key1 = Unit,
+ effect = {
+ val listener = object : KotlinPlayer.Listener {
+
+ override fun onMediaStatus(previous: MediaStatus, next: MediaStatus) {
+ if (state.mediaInfo == null && next.isPrepared) {
+ state.mediaInfo = player.mediaInfo
+ state.audioTrack = if (state.mediaInfo?.audio.isNullOrEmpty()) -1 else 0
+ state.subtitleTrack = if (state.mediaInfo?.subtitles.isNullOrEmpty()) -1 else 0
+ state.videoTrack = if (state.mediaInfo?.video.isNullOrEmpty()) -1 else 0
+ }
+ }
+
+ override fun onState(newValue: Int) {
+ state.state = newValue
+ }
+ }
+ player.listeners += listener
+ onDispose { player.listeners -= listener }
+ }
+ )
+ return state
+}
+
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun TracksDialog(
+ state: PlayerState,
+ type: MediaType,
+ onDismiss: () -> Unit,
+ onClick: (index: Int, stream: MdkStream) -> Unit,
+) {
+ val isVisible by rememberUpdatedState(type != MediaType.Unknown)
+ val tracks by remember(type) {
+ derivedStateOf {
+ when (type) {
+ MediaType.Unknown -> emptyList()
+ MediaType.Audio -> state.mediaInfo?.audio.orEmpty()
+ MediaType.Video -> state.mediaInfo?.video.orEmpty()
+ MediaType.Subtitles -> state.mediaInfo?.subtitles.orEmpty()
+ }
+ }
+ }
+ if (isVisible) {
+ BasicAlertDialog(
+ onDismissRequest = onDismiss,
+ content = {
+ Surface(
+ modifier = Modifier.fillMaxWidth()
+ .fillMaxHeight(.5f),
+ shape = MaterialTheme.shapes.medium,
+ content = {
+ Column(
+ modifier = Modifier.fillMaxSize()
+ .verticalScroll(
+ state = rememberScrollState()
+ ),
+ content = {
+ for (index in tracks.indices) {
+ val track = tracks[index]
+ val isSelected by remember {
+ derivedStateOf {
+ when (track) {
+ is MdkAudioStream -> index == state.audioTrack
+ is MdkSubtitle -> index == state.subtitleTrack
+ is MdkVideoStream -> index == state.videoTrack
+ }
+ }
+ }
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .clickable {
+ onClick.invoke(index, track)
+ }
+ .padding(16.dp),
+ content = {
+ Text(
+ modifier = Modifier.weight(1f),
+ text = "Track ${track.index}"
+ )
+ if (isSelected) {
+ Icon(
+ imageVector = Icons.Default.Check,
+ contentDescription = null,
+ )
+ }
+ }
+ )
+ }
+ }
+ )
+ }
+ )
+ }
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mediadevkit/mdkplayer/RootScreen.kt b/app/src/main/java/com/mediadevkit/mdkplayer/RootScreen.kt
new file mode 100644
index 0000000..a6c4de5
--- /dev/null
+++ b/app/src/main/java/com/mediadevkit/mdkplayer/RootScreen.kt
@@ -0,0 +1,110 @@
+package com.mediadevkit.mdkplayer
+
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.contract.*
+import androidx.compose.foundation.layout.*
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.ui.*
+import androidx.compose.ui.unit.dp
+import com.mediadevkit.sdk.KotlinPlayer
+
+var decodeToSurfaceView by mutableStateOf(false)
+
+@Composable
+fun RootScreen(
+ modifier: Modifier = Modifier,
+) {
+ val (getUrl, setUrl) = remember { mutableStateOf(defaultUrl) }
+
+ val intentLauncher = rememberLauncherForActivityResult(
+ contract = ActivityResultContracts.GetContent(),
+ onResult = { uri ->
+ if (uri == null) return@rememberLauncherForActivityResult
+ Navigator.currentScreen = Screen.Player(
+ url = uri.toString(),
+ config = KotlinPlayer.Config(decodeToSurfaceView),
+ )
+ }
+ )
+ Column(
+ modifier = modifier
+ .fillMaxSize(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(20.dp, alignment = Alignment.CenterVertically),
+ content = {
+ Row(
+ modifier = Modifier,
+ horizontalArrangement = Arrangement.spacedBy(12.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ content = {
+ Text("Decode to SurfaceView")
+ Switch(
+ checked = decodeToSurfaceView,
+ onCheckedChange = { decodeToSurfaceView = it },
+ )
+ }
+ )
+ Button(
+ onClick = { intentLauncher.launch("video/*") },
+ content = { Text("Open file") }
+ )
+ HorizontalDivider()
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(12.dp),
+ content = {
+ OutlinedTextField(
+ modifier = Modifier.weight(1f),
+ value = getUrl,
+ onValueChange = setUrl,
+ label = { Text("Url") },
+ singleLine = true,
+ )
+ Button(
+ shape = MaterialTheme.shapes.small,
+ onClick = {
+ if ( getUrl.isBlank() ) return@Button
+ Navigator.currentScreen = Screen.Player(
+ url = getUrl,
+ config = KotlinPlayer.Config(decodeToSurfaceView)
+ )
+ },
+ content = { Text("Go") }
+ )
+ }
+ )
+
+
+ },
+ )
+}
+
+
+
+
+
+//dv profile 5 fhd
+//BL_RPU_dvhe-05_1920x1080@24fps_0_6313.mp4
+
+//dv profile 5 uhd
+//BL_RPU_dvhe-05_3840x2160@24fps_0_6313.mp4
+
+//dv profile 8.4 fhd
+//BL_RPU_dvhe-08-84_1920x1080@24fps_0_6313.mp4
+
+//dv profile 8.4 uhd
+//BL_RPU_dvhe-08-84_3840x2160@24fps_0_6313.mp4
+
+//dv profile 8.1 fhd
+//BL_RPU_dvhe-08-mapDynamic1000-81_1920x1080@24fps_0_6313.mp4
+
+//dv profile 8.1 uhd
+//BL_RPU_dvhe-08-mapDynamic1000-81_3840x2160@24fps_0_6313.mp4
+
+val baseUrl = "https://media.githubusercontent.com/media/DolbyLaboratories/dolby-vision-contents/refs/heads/main/SolLevante_Netflix/{FILE}?download=true"
+//val defaultUrl = baseUrl.replace("{FILE}", "BL_RPU_dvhe-08-mapDynamic1000-81_1920x1080@24fps_0_6313.mp4")
+val defaultUrl = "https://github.com/ietf-wg-cellar/matroska-test-files/raw/master/test_files/test5.mkv"
\ No newline at end of file
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
deleted file mode 100644
index 2b068d1..0000000
--- a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
deleted file mode 100644
index 07d5da9..0000000
--- a/app/src/main/res/drawable/ic_launcher_background.xml
+++ /dev/null
@@ -1,170 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..869a948
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
deleted file mode 100644
index 448a9a4..0000000
--- a/app/src/main/res/layout/activity_main.xml
+++ /dev/null
@@ -1,84 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
index eca70cf..7353dbd 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -1,5 +1,5 @@
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
deleted file mode 100644
index eca70cf..0000000
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
deleted file mode 100644
index a571e60..0000000
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
deleted file mode 100644
index 61da551..0000000
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
deleted file mode 100644
index c41dd28..0000000
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
deleted file mode 100644
index db5080a..0000000
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
deleted file mode 100644
index 6dba46d..0000000
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
deleted file mode 100644
index da31a87..0000000
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
deleted file mode 100644
index 15ac681..0000000
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
deleted file mode 100644
index b216f2d..0000000
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
deleted file mode 100644
index f25a419..0000000
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
deleted file mode 100644
index e96783c..0000000
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
deleted file mode 100644
index 32aaabe..0000000
--- a/app/src/main/res/values-night/themes.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
deleted file mode 100644
index f8c6127..0000000
--- a/app/src/main/res/values/colors.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
- #FFBB86FC
- #FF6200EE
- #FF3700B3
- #FF03DAC5
- #FF018786
- #FF000000
- #FFFFFFFF
-
\ No newline at end of file
diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml
new file mode 100644
index 0000000..c5d5899
--- /dev/null
+++ b/app/src/main/res/values/ic_launcher_background.xml
@@ -0,0 +1,4 @@
+
+
+ #FFFFFF
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
deleted file mode 100644
index 8be9259..0000000
--- a/app/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
- MDKPlayer
-
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index fa10812..4ebdb52 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -1,16 +1,5 @@
-
-
-
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
deleted file mode 100644
index 9506c1b..0000000
--- a/build.gradle
+++ /dev/null
@@ -1,29 +0,0 @@
-// Top-level build file where you can add configuration options common to all sub-projects/modules.
-buildscript {
- repositories {
- maven{ url = "https://maven.aliyun.com/nexus/content/groups/public/" }
- google()
- jcenter()
- }
- dependencies {
- classpath 'com.android.tools.build:gradle:8.2.0'
-
- // NOTE: Do not place your application dependencies here; they belong
- // in the individual module build.gradle files
- }
-}
-
-allprojects {
- repositories {
- maven{ url = "https://maven.aliyun.com/nexus/content/groups/public/" }
- google()
- jcenter()
- flatDir {
- dirs 'libs'
- }
- }
-}
-
-task clean(type: Delete) {
- delete rootProject.buildDir
-}
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..f0a105b
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,13 @@
+plugins {
+ alias(libs.plugins.androidApplication) apply false
+ alias(libs.plugins.androidLibrary) apply false
+ alias(libs.plugins.composeCompiler) apply false
+ alias(libs.plugins.jetbrains.kotlin.jvm) apply false
+ alias(libs.plugins.kotlin.android) apply false
+}
+
+buildscript {
+ dependencies {
+ classpath(libs.android.buildTools.gradle)
+ }
+}
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
new file mode 100644
index 0000000..38c9085
--- /dev/null
+++ b/gradle/libs.versions.toml
@@ -0,0 +1,57 @@
+[versions]
+agp = "8.5.0"
+compileSdk = "35"
+minSdk = "21"
+targetSdk = "35"
+buildTools = "35.0.0"
+ndk = "27.2.12479018"
+cmake = "3.22.1"
+kotlin = "2.0.21"
+kotlinx-coroutines = "1.9.0"
+jetbrainsKotlinJvm = "2.0.21"
+compose = "1.7.6"
+
+[libraries]
+kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
+android-buildTools-gradle = { module = "com.android.tools.build:gradle", version.ref = "agp" }
+androidx-core = { group = "androidx.core", name = "core-ktx", version = "1.15.0" }
+androidx-activityCompose = { module = "androidx.activity:activity-compose", version = "1.10.0" }
+androidx-lifecycle = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version = "2.8.7"}
+androidx-appcompat = { group = "androidx.appcompat", name ="appcompat", version = "1.7.0"}
+
+compose-runtime = { group = "androidx.compose.runtime", name = "runtime", version.ref = "compose"}
+compose-foundation = { group = "androidx.compose.foundation", name = "foundation", version.ref = "compose" }
+compose-foundation-layout = { group = "androidx.compose.foundation", name = "foundation-layout", version.ref = "compose" }
+compose-ui-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "compose" }
+compose-ui-geometry = { group = "androidx.compose.ui", name = "ui-geometry", version.ref = "compose" }
+compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics", version.ref = "compose" }
+compose-ui-text = { group = "androidx.compose.ui", name = "ui-text", version.ref = "compose" }
+compose-ui-unit = { group = "androidx.compose.ui", name = "ui-unit", version.ref = "compose" }
+compose-ui-util = { group = "androidx.compose.ui", name = "ui-util", version.ref = "compose" }
+compose-material3 = { group = "androidx.compose.material3", name = "material3", version = "1.3.1" }
+compose-material-icons = { group = "androidx.compose.material", name = "material-icons-core", version.ref = "compose" }
+compose-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended", version.ref = "compose" }
+
+[plugins]
+androidApplication = { id = "com.android.application", version.ref = "agp" }
+androidLibrary = { id = "com.android.library", version.ref = "agp" }
+composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
+jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "jetbrainsKotlinJvm" }
+kotlin-android = { id = "org.jetbrains.kotlin.android", version = "2.0.20" }
+
+[bundles]
+compose = [
+ "compose-runtime",
+ "compose-foundation",
+ "compose-foundation-layout",
+ "compose-ui-ui",
+ "compose-ui-geometry",
+ "compose-ui-graphics",
+ "compose-ui-text",
+ "compose-ui-unit",
+ "compose-ui-util",
+ "compose-material3",
+ "compose-material-icons",
+ "compose-material-icons-extended",
+]
+
diff --git a/sdk/build.gradle b/sdk/build.gradle
deleted file mode 100644
index 809c4a3..0000000
--- a/sdk/build.gradle
+++ /dev/null
@@ -1,63 +0,0 @@
-plugins {
- id 'com.android.library'
-}
-
-android {
- compileSdk 35
- buildToolsVersion '35.0.0'
-
-
- defaultConfig {
- minSdkVersion 21
- targetSdkVersion 35
-
-// testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- consumerProguardFiles "consumer-rules.pro"
-
- externalNativeBuild {
- cmake {
- cppFlags ""
- arguments '-DANDROID_STL=c++_shared' //, "-D..."
- }
- }
- ndk {
- // Specifies the ABI configurations of your native
- // libraries Gradle should build and package with your APK.
- abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64'
- }
- ndkVersion "27.2.12479018"
- }
-
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
- jniDebuggable true
- }
- }
- externalNativeBuild {
- cmake {
- path "src/main/cpp/CMakeLists.txt"
- version "3.31.2"
- }
- }
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_11
- targetCompatibility JavaVersion.VERSION_11
- }
- ndkVersion '27.2.12479018'
- packagingOptions {
- jniLibs {
- useLegacyPackaging true
- }
- }
- namespace 'com.mediadevkit.sdk'
-}
-
-dependencies {
-
- implementation 'androidx.appcompat:appcompat:1.6.1'
- implementation 'com.google.android.material:material:1.8.0'
- //androidTestImplementation 'androidx.test.ext:junit:1.1.5'
- //androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
-}
\ No newline at end of file
diff --git a/sdk/build.gradle.kts b/sdk/build.gradle.kts
new file mode 100644
index 0000000..cf4c343
--- /dev/null
+++ b/sdk/build.gradle.kts
@@ -0,0 +1,57 @@
+plugins {
+ alias(libs.plugins.androidLibrary)
+ alias(libs.plugins.kotlin.android)
+}
+
+android {
+ namespace = "com.mediadevkit.sdk"
+ compileSdk = libs.versions.compileSdk.get().toInt()
+ buildToolsVersion = libs.versions.buildTools.get()
+
+ defaultConfig {
+ minSdk = libs.versions.minSdk.get().toInt()
+ consumerProguardFiles += file("consumer-rules.pro")
+ externalNativeBuild {
+ cmake {
+ //cppFlags += ""
+ arguments += "-DANDROID_STL=c++_shared"
+ }
+ ndkVersion = libs.versions.ndk.get()
+ }
+ }
+
+ buildTypes {
+ val release by getting {
+ isMinifyEnabled = false
+ isJniDebuggable = true
+ proguardFiles += listOf(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ file("proguard-rules.pro"),
+ )
+ }
+ }
+
+ externalNativeBuild {
+ cmake {
+ path = file("src/main/cpp/CMakeLists.txt")
+ version = libs.versions.cmake.get()
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+ kotlinOptions {
+ jvmTarget = "11"
+ }
+
+
+}
+
+dependencies {
+ api(libs.androidx.core)
+ api(libs.kotlinx.coroutines.core)
+ implementation(libs.androidx.core)
+}
+
diff --git a/sdk/src/main/cpp/MDKPlayerJNI.cpp b/sdk/src/main/cpp/MDKPlayerJNI.cpp
index 598d021..6a954d8 100644
--- a/sdk/src/main/cpp/MDKPlayerJNI.cpp
+++ b/sdk/src/main/cpp/MDKPlayerJNI.cpp
@@ -14,6 +14,9 @@
#define DECODE_TO_SURFACEVIEW 0
#define USE_VULKAN 0
+#define LOG_TAG "mdk-kni"
+#define log_info(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
+
enum { // custom enum
MEDIA_ERROR = -1,
MEDIA_INFO,
@@ -71,14 +74,14 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
std::clog << "JNI_OnLoad" << std::endl;
JNIEnv* env = nullptr;
- if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK || !env) {
+ if (vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK || !env) {
std::clog << "GetEnv for JNI_VERSION_1_4 failed" << std::endl;
return -1;
}
jmi::javaVM(vm);
- //SetGlobalOption("JavaVM", vm);
- return JNI_VERSION_1_4;
+ SetGlobalOption("JavaVM", vm);
+ return JNI_VERSION_1_6;
}
void JNI_OnUnload(JavaVM* vm, void* reserved)
@@ -296,4 +299,439 @@ MDK_JNI(void, MDKPlayer_nativeSetColorSpace, jint value)
p->set(ColorSpace(value)); // store default value globally, will be used if surface is changed
p->set(ColorSpace(value), r->surface); // apply for current surface
}
-}
\ No newline at end of file
+}
+
+
+// Kotlin bindings
+
+struct JavaMediaInfo {
+ jclass mediaInfoClass;
+ jmethodID mediaInfoConstructor;
+
+ jclass videoStreamClass;
+ jclass videoCodecClass;
+ jmethodID videoStreamConstructor;
+ jmethodID videoCodecConstructor;
+
+ jclass audioStreamClass;
+ jclass audioCodecClass;
+ jmethodID audioStreamConstructor;
+ jmethodID audioCodecConstructor;
+
+ jclass subtitleStreamClass;
+ jclass subtitleCodecClass;
+ jmethodID subtitleStreamConstructor;
+ jmethodID subtitleCodecConstructor;
+
+ jclass hashMapClass;
+ jmethodID hashMapConstructor;
+ jmethodID hashMapPut;
+
+ jclass arrayList;
+ jmethodID arrayListConstructor;
+ jmethodID arrayListAdd;
+};
+
+static JavaMediaInfo javaMediaInfo(JNIEnv* env) {
+ auto mediaInfo = env->FindClass("com/mediadevkit/sdk/MdkMediaInfo");
+ auto videoStream = env->FindClass("com/mediadevkit/sdk/MdkVideoStream");
+ auto videoCodec = env->FindClass("com/mediadevkit/sdk/MdkVideoCodec");
+ auto audioStream = env->FindClass("com/mediadevkit/sdk/MdkAudioStream");
+ auto audioCodec = env->FindClass("com/mediadevkit/sdk/MdkAudioCodec");
+ auto subtitleStream = env->FindClass("com/mediadevkit/sdk/MdkSubtitle");
+ auto subtitleCodec = env->FindClass("com/mediadevkit/sdk/MdkSubtitleCodec");
+ auto hashMapClass = env->FindClass("java/util/HashMap");
+ auto arrayListClass = env->FindClass("java/util/ArrayList");
+
+ return JavaMediaInfo{
+ .mediaInfoClass = (jclass) env->NewGlobalRef(mediaInfo),
+ .mediaInfoConstructor = env->GetMethodID(mediaInfo, "", "(JJJJLjava/lang/String;ILjava/util/List;Ljava/util/List;Ljava/util/List;)V"),
+ .videoStreamClass = (jclass) env->NewGlobalRef(videoStream),
+ .videoCodecClass = (jclass) env->NewGlobalRef(videoCodec),
+ .videoStreamConstructor = env->GetMethodID(videoStream, "", "(IJJJILjava/util/Map;Lcom/mediadevkit/sdk/MdkVideoCodec;)V"),
+ .videoCodecConstructor = env->GetMethodID(videoCodec, "", "(Ljava/lang/String;IJIIFILjava/lang/String;IIIFII)V"),
+ .audioStreamClass = (jclass) env->NewGlobalRef(audioStream),
+ .audioCodecClass = (jclass) env->NewGlobalRef(audioCodec),
+ .audioStreamConstructor = env->GetMethodID(audioStream, "", "(IJJJLjava/util/Map;Lcom/mediadevkit/sdk/MdkAudioCodec;)V"),
+ .audioCodecConstructor = env->GetMethodID(audioCodec, "", "(Ljava/lang/String;IJIIFZZZIIIII)V"),
+ .subtitleStreamClass = (jclass) env->NewGlobalRef(subtitleStream),
+ .subtitleCodecClass = (jclass) env->NewGlobalRef(subtitleCodec),
+ .subtitleStreamConstructor = env->GetMethodID(subtitleStream, "", "(IJJLjava/util/Map;Lcom/mediadevkit/sdk/MdkSubtitleCodec;)V"),
+ .subtitleCodecConstructor = env->GetMethodID(subtitleCodec, "", "(Ljava/lang/String;I)V"),
+ .hashMapClass = (jclass) env->NewGlobalRef(hashMapClass),
+ .hashMapConstructor = env->GetMethodID(hashMapClass, "", "()V"),
+ .hashMapPut = env->GetMethodID(hashMapClass, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"),
+ .arrayList = (jclass) env->NewGlobalRef(arrayListClass),
+ .arrayListConstructor = env->GetMethodID(arrayListClass, "", "(I)V"),
+ .arrayListAdd = env->GetMethodID(arrayListClass, "add", "(ILjava/lang/Object;)V"),
+ };
+}
+
+static jobject buildMediaInfo(
+ JNIEnv *env,
+ mdk::MediaInfo info
+) {
+
+ auto mediaInfoDef = javaMediaInfo(env);
+ auto videos = env->NewObject(mediaInfoDef.arrayList, mediaInfoDef.arrayListConstructor, (jint) info.video.size());
+ auto audios = env->NewObject(mediaInfoDef.arrayList, mediaInfoDef.arrayListConstructor, (jint) info.audio.size());
+ auto subtitles = env->NewObject(mediaInfoDef.arrayList, mediaInfoDef.arrayListConstructor, (jint) info.subtitle.size());
+
+
+ for (int i = 0; i < info.video.size(); i++) {
+ auto track = info.video[i];
+ auto params = track.codec;
+ auto metaData = env->NewObject(mediaInfoDef.hashMapClass, mediaInfoDef.hashMapConstructor);
+
+ for (const auto& pair : track.metadata) {
+ auto key = env->NewStringUTF(pair.first.c_str());
+ auto value = env->NewStringUTF(pair.second.c_str());
+ env->CallObjectMethod(metaData, mediaInfoDef.hashMapPut, key, value);
+ }
+
+ auto javaCodec = env->NewObject(
+ mediaInfoDef.videoCodecClass,
+ mediaInfoDef.videoCodecConstructor,
+ /* codec = */ env->NewStringUTF(params.codec),
+ /* codecTag = */ static_cast(params.codec_tag),
+ /* bitRate = */ params.bit_rate,
+ /* profile = */ params.profile,
+ /* level = */ params.level,
+ /* frameRate = */ params.frame_rate,
+ /* format = */ params.format,
+ /* formatName = */ env->NewStringUTF(params.format_name),
+ /* width = */ params.width,
+ /* height = */ params.height,
+ /* bFrames = */ params.b_frames,
+ /* par = */ params.par,
+ /* colorSpace = */ (jint) params.color_space,
+ /* dolbyVisionProfile = */ (jint) params.dovi_profile
+ );
+ auto javaStream = env->NewObject(
+ mediaInfoDef.videoStreamClass,
+ mediaInfoDef.videoStreamConstructor,
+ /* index = */ track.index,
+ /* startTime = */ track.start_time,
+ /* duration = */ track.duration,
+ /* frames = */ track.frames,
+ /* rotation = */ track.rotation,
+ /* metaData = */ metaData,
+ /* codec = */ javaCodec
+ );
+ env->CallVoidMethod(videos, mediaInfoDef.arrayListAdd, i, javaStream);
+ }
+
+ for (int i = 0; i < info.subtitle.size(); i++) {
+ auto track = info.subtitle[i];
+ auto params = track.codec;
+ auto metaData = env->NewObject(mediaInfoDef.hashMapClass, mediaInfoDef.hashMapConstructor);
+ for (const auto& pair : track.metadata) {
+ auto key = env->NewStringUTF(pair.first.c_str());
+ auto value = env->NewStringUTF(pair.second.c_str());
+ env->CallObjectMethod(metaData, mediaInfoDef.hashMapPut, key, value);
+ }
+ auto javaCodec = env->NewObject(
+ /* clazz = */ mediaInfoDef.subtitleCodecClass,
+ /* methodID = */ mediaInfoDef.subtitleCodecConstructor,
+ /* codec = */ env->NewStringUTF(params.codec),
+ /* codecTag = */ static_cast(params.codec_tag)
+ );
+ auto javaStream = env->NewObject(
+ /* clazz = */ mediaInfoDef.subtitleStreamClass,
+ /* methodID = */ mediaInfoDef.subtitleStreamConstructor,
+ /* index = */ track.index,
+ /* duration = */ track.duration,
+ /* startTime = */ track.start_time,
+ /* metaData = */ metaData,
+ /* codec = */ javaCodec
+ );
+ env->CallVoidMethod(subtitles, mediaInfoDef.arrayListAdd, i, javaStream);
+ }
+
+ for (int i = 0; i < info.audio.size(); i++) {
+ auto track = info.audio[i];
+ auto params = track.codec;
+ auto metaData = env->NewObject(mediaInfoDef.hashMapClass, mediaInfoDef.hashMapConstructor);
+ for (const auto& pair : track.metadata) {
+ auto key = env->NewStringUTF(pair.first.c_str());
+ auto value = env->NewStringUTF(pair.second.c_str());
+ env->CallObjectMethod(metaData, mediaInfoDef.hashMapPut, key, value);
+ }
+ auto javaCodec = env->NewObject(
+ /* clazz = */ mediaInfoDef.audioCodecClass,
+ /* methodID = */ mediaInfoDef.audioCodecConstructor,
+ /* codec = */ env->NewStringUTF(params.codec),
+ /* codecTag = */ static_cast(params.codec_tag),
+ /* bitRate = */ params.bit_rate,
+ /* profile = */ params.profile,
+ /* level = */ params.level,
+ /* frameRate = */ params.frame_rate,
+ /* isFloat = */ params.is_float,
+ /* isUnsigned = */ params.is_unsigned,
+ /* isPlanar = */ params.is_planar,
+ /* rawSampleSize = */ params.raw_sample_size,
+ /* channels = */ params.channels,
+ /* sampleRate = */ params.sample_rate,
+ /* blockAlign = */ params.block_align,
+ /* frameSize = */ params.frame_size
+ );
+ auto javaStream = env->NewObject(
+ /* clazz = */ mediaInfoDef.audioStreamClass,
+ /* methodID = */ mediaInfoDef.audioStreamConstructor,
+ /* index = */ track.index,
+ /* startTime = */ track.start_time,
+ /* duration = */ track.duration,
+ /* frames = */ track.frames,
+ /* metaData = */ metaData,
+ /* codec = */ javaCodec
+ );
+ env->CallVoidMethod(audios, mediaInfoDef.arrayListAdd, i, javaStream);
+ }
+
+ return env->NewObject(
+ /* clazz = */ mediaInfoDef.mediaInfoClass,
+ /* methodID = */ mediaInfoDef.mediaInfoConstructor,
+ /* startTime = */ info.start_time,
+ /* duration = */ info.duration,
+ /* bitRate = */ info.bit_rate,
+ /* size = */ info.size,
+ /* format = */ env->NewStringUTF(info.format),
+ /* streams = */ info.streams,
+ /* audio = */ audios,
+ /* video = */ videos,
+ /* subtitles = */ subtitles
+ );
+}
+
+extern "C" {
+
+ JNIEXPORT jlong JNICALL
+ Java_com_mediadevkit_sdk_LibMdk_newGlobalRef(
+ JNIEnv *env,
+ jclass clazz,
+ jobject obj
+ ) {
+ if (obj == nullptr) return 0L;
+ return (intptr_t) env->NewGlobalRef(obj);
+ }
+
+ JNIEXPORT void JNICALL
+ Java_com_mediadevkit_sdk_LibMdk_deleteGlobalRef(
+ JNIEnv *env,
+ jclass clazz,
+ jlong ref
+ ) {
+ if (ref == 0L) return;
+ env->DeleteGlobalRef( (jobject) ref);
+ }
+
+ JNIEXPORT jlong JNICALL
+ Java_com_mediadevkit_sdk_LibMdk_createPlayer(
+ JNIEnv *env,
+ jclass clazz
+ ) {
+ auto ref = new PlayerRef();
+ return (jlong) ref;
+ }
+
+ JNIEXPORT void JNICALL
+ Java_com_mediadevkit_sdk_LibMdk_setState(
+ JNIEnv *env,
+ jclass clazz,
+ jlong handle,
+ jint state
+ ) {
+ if (handle == 0L) return;
+ auto player = get(handle);
+ player->set( (mdk::State) state);
+ }
+
+ JNIEXPORT jint JNICALL
+ Java_com_mediadevkit_sdk_LibMdk_getState(
+ JNIEnv *env,
+ jclass clazz,
+ jlong handle
+ ) {
+ if (handle == 0L) return (jint) mdk::State::NotRunning;
+ auto player = get(handle);
+ return (jint) player->state();
+ }
+
+ JNIEXPORT void JNICALL
+ Java_com_mediadevkit_sdk_LibMdk_setColorSpace(
+ JNIEnv *env,
+ jclass clazz,
+ jlong handle,
+ jlong surface,
+ jint color_space
+ ) {
+ if (handle == 0L) return;
+ auto player = get(handle);
+ player->set( (mdk::ColorSpace) color_space, surface == 0L ? nullptr : (void*) surface);
+ }
+
+ JNIEXPORT void JNICALL
+ Java_com_mediadevkit_sdk_LibMdk_setMedia(
+ JNIEnv *env,
+ jclass clazz,
+ jlong handle,
+ jstring url
+ ) {
+ if (handle == 0L) return;
+ auto player = get(handle);
+ if (url == nullptr) {
+ player->setMedia(nullptr);
+ } else {
+ auto cUrl = env->GetStringUTFChars(url, nullptr);
+ player->setMedia(cUrl);
+ env->ReleaseStringUTFChars(url, cUrl);
+ }
+ }
+
+ JNIEXPORT void JNICALL
+ Java_com_mediadevkit_sdk_LibMdk_release(
+ JNIEnv *env,
+ jclass clazz,
+ jlong handle
+ ) {
+ if (handle == 0L) return;
+ delete (PlayerRef*) handle;
+ }
+
+ JNIEXPORT jlong JNICALL
+ Java_com_mediadevkit_sdk_LibMdk_getPosition(
+ JNIEnv *env,
+ jclass clazz,
+ jlong handle
+ ) {
+ if (handle == 0L) return 0L;
+ auto player = get(handle);
+ return player->position();
+ }
+
+ JNIEXPORT void JNICALL
+ Java_com_mediadevkit_sdk_LibMdk_seek(
+ JNIEnv *env,
+ jclass clazz,
+ jlong handle,
+ jlong position
+ ) {
+ if (handle == 0L) return;
+ auto player = get(handle);
+ player->seek(position);
+ }
+
+ JNIEXPORT void JNICALL
+ Java_com_mediadevkit_sdk_LibMdk_setProperty(
+ JNIEnv *env,
+ jclass clazz,
+ jlong handle,
+ jstring key,
+ jstring value
+ ) {
+ if (handle == 0L) return;
+ auto player = get(handle);
+ auto cKey = env->GetStringUTFChars(key, nullptr);
+ auto cValue = env->GetStringUTFChars(value, nullptr);
+ player->setProperty(cKey, cValue);
+ env->ReleaseStringUTFChars(key, cKey);
+ env->ReleaseStringUTFChars(value, cValue);
+ }
+
+ JNIEXPORT void JNICALL
+ Java_com_mediadevkit_sdk_LibMdk_updateNativeSurface(
+ JNIEnv *env,
+ jclass clazz,
+ jlong handle,
+ jlong surface,
+ jint width,
+ jint height,
+ jint type
+ ) {
+ if (handle == 0L) return;
+ log_info("player->updateNativeSurface handle=%lld, surface=%lld, width=%d, height=%d, type=%d", handle, surface, width, height, type);
+ auto player = get(handle);
+ player->updateNativeSurface(
+ /* surface = */ surface == 0L ? nullptr : (jobject) surface,
+ /* width = */ width,
+ /* height = */ height,
+ /* type = */ (mdk::Player::SurfaceType) type
+ );
+ log_info("done");
+ }
+
+
+ JNIEXPORT void JNICALL
+ Java_com_mediadevkit_sdk_LibMdk_setListener(
+ JNIEnv *env,
+ jclass clazz,
+ jlong handle,
+ jobject listener
+ ) {
+ if (handle == 0L) return;
+ auto player = get(handle);
+ auto weak = env->NewWeakGlobalRef(listener);
+ auto onMediaStatusMethod = env->GetMethodID(env->GetObjectClass(listener),"onMediaStatus","(II)V");
+ auto onStateMethod = env->GetMethodID(env->GetObjectClass(listener),"onState","(I)V");
+ player->onMediaStatus(
+ /* cb = */ [weak, onMediaStatusMethod] (mdk::MediaStatus prev, mdk::MediaStatus next) {
+ auto env = jmi::getEnv();
+ auto isValid = !env->IsSameObject(weak, nullptr);
+ if (!isValid) return false;
+ env->CallVoidMethod(weak, onMediaStatusMethod, prev, next);
+ return true;
+ },
+ /* token = */ nullptr
+ );
+ player->onStateChanged(
+ /* cb = */ [weak, onStateMethod] (mdk::State state) {
+ auto env = jmi::getEnv();
+ auto isValid = !env->IsSameObject(weak, nullptr);
+ if (!isValid) return false;
+ env->CallVoidMethod(weak, onStateMethod, state);
+ return true;
+ }
+ );
+ }
+
+
+ JNIEXPORT jobject JNICALL
+ Java_com_mediadevkit_sdk_LibMdk_getMediaInfo(
+ JNIEnv *env,
+ jclass clazz,
+ jlong handle
+ ) {
+ if (handle == 0L) return nullptr;
+ auto player = get(handle);
+ return buildMediaInfo(env, player->mediaInfo());
+ }
+
+ JNIEXPORT void JNICALL
+ Java_com_mediadevkit_sdk_LibMdk_setGlobalOptionString(
+ JNIEnv *env,
+ jclass clazz,
+ jstring key,
+ jstring value
+ ) {
+ auto cKey = env->GetStringUTFChars(key, nullptr);
+ auto cValue = env->GetStringUTFChars(value, nullptr);
+ SetGlobalOption(cKey, cValue);
+ env->ReleaseStringUTFChars(key, cKey);
+ env->ReleaseStringUTFChars(value, cValue);
+ }
+
+ JNIEXPORT void JNICALL
+ Java_com_mediadevkit_sdk_LibMdk_setGlobalOptionInt(
+ JNIEnv *env,
+ jclass clazz,
+ jstring key,
+ jint value
+ ) {
+ auto cKey = env->GetStringUTFChars(key, nullptr);
+ SetGlobalOption(cKey, value);
+ env->ReleaseStringUTFChars(key, cKey);
+ }
+
+}
+
+
diff --git a/sdk/src/main/java/com/mediadevkit/sdk/KotlinPlayer.kt b/sdk/src/main/java/com/mediadevkit/sdk/KotlinPlayer.kt
new file mode 100644
index 0000000..d135049
--- /dev/null
+++ b/sdk/src/main/java/com/mediadevkit/sdk/KotlinPlayer.kt
@@ -0,0 +1,125 @@
+package com.mediadevkit.sdk
+
+import android.os.Handler
+import android.os.Looper
+import android.view.*
+
+class KotlinPlayer(private val config: Config) {
+
+ //events from native can occur on background thread, so post to listeners with this
+ private val handler = Handler( Looper.getMainLooper() )
+
+ interface Listener {
+ fun onState(newValue: Int)
+ fun onMediaStatus(previous: Int, next: Int) = onMediaStatus( MediaStatus(previous), MediaStatus(next) )
+ fun onMediaStatus(previous: MediaStatus, next: MediaStatus)
+ }
+
+ data class Config(val decodeToSurfaceView: Boolean)
+
+ private var nativeHandle = LibMdk.createPlayer()
+
+ val listeners = mutableSetOf()
+
+ private val listener = object : Listener {
+ override fun onState(newValue: Int) {
+ handler.post { for (it in listeners) it.onState(newValue) }
+ }
+ override fun onMediaStatus(previous: MediaStatus, next: MediaStatus) {
+ handler.post {
+ for (it in listeners) it.onMediaStatus(previous, next)
+ }
+ }
+ }
+
+ /**
+ * if set subtitle track with decodeToSurfaceView == true && AMediaCodec:async=1: freeze
+ */
+ init {
+ LibMdk.setListener(nativeHandle, listener)
+ val videoDecoders = listOf(
+ "AMediaCodec:dv=1:acquire=latest:ndk_codec=1:java=0:copy=0:surface=1:image=1:async=1:low_latency=1",
+ "FFmpeg",
+ )
+ LibMdk.setProperty(
+ handle = nativeHandle,
+ key = "video.decoders",
+ value = videoDecoders.joinToString(separator = ",")
+ )
+ }
+
+ var nativeWindow = 0L
+ set(value) {
+ if (config.decodeToSurfaceView) {
+ LibMdk.setProperty(nativeHandle, "video.decoder", "surface=$value")
+ }
+ LibMdk.deleteGlobalRef(field)
+ field = value
+ }
+
+ var state: Int
+ get() = LibMdk.getState(nativeHandle)
+ set(value) = LibMdk.setState(nativeHandle, value)
+
+ var media: String? = null
+ set(value) {
+ field = value
+ LibMdk.setMedia(nativeHandle, value)
+ }
+
+ var position: Long
+ get() = LibMdk.getPosition(nativeHandle)
+ set(value) = LibMdk.seek(nativeHandle, value)
+
+ var hdr: Boolean = false
+ set(value) {
+ field = value
+ LibMdk.setColorSpace(nativeHandle, nativeWindow, if (hdr) 1 else 0)
+ }
+
+ //var hardwareDecoding: Boolean = false
+
+ val mediaInfo: MdkMediaInfo?
+ get() = LibMdk.getMediaInfo(nativeHandle)
+
+ fun setSurface(view: SurfaceView?) {
+ this.surfaceView = view
+ }
+
+ fun release() {
+ LibMdk.release(nativeHandle)
+ }
+
+ fun setProperty(key: String, value: String) = LibMdk.setProperty(nativeHandle, key, value)
+
+ val callback = object : SurfaceHolder.Callback {
+
+ override fun surfaceCreated(holder: SurfaceHolder) {
+ nativeWindow = LibMdk.newGlobalRef(holder.surface)
+ if (config.decodeToSurfaceView) return
+ LibMdk.updateNativeSurface(handle = nativeHandle, 0L, 0, 0, 0)
+ LibMdk.updateNativeSurface(handle = nativeHandle, nativeWindow, -1, -1, 0)
+ }
+
+ override fun surfaceDestroyed(holder: SurfaceHolder) {
+ nativeWindow = 0L
+ if (config.decodeToSurfaceView) return
+ LibMdk.updateNativeSurface(handle = nativeHandle, 0L, 0, 0, 0)
+ }
+
+ override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
+ if (config.decodeToSurfaceView) return
+ LibMdk.updateNativeSurface(handle = nativeHandle, nativeWindow, width, height, 0)
+ }
+ }
+
+ private var surfaceView: SurfaceView? = null
+ set(newValue) {
+ if (field === newValue) return
+ field?.holder?.removeCallback(callback)
+ newValue?.holder?.addCallback(callback)
+ field = newValue
+ }
+
+}
+
diff --git a/sdk/src/main/java/com/mediadevkit/sdk/LibMdk.kt b/sdk/src/main/java/com/mediadevkit/sdk/LibMdk.kt
new file mode 100644
index 0000000..94f900d
--- /dev/null
+++ b/sdk/src/main/java/com/mediadevkit/sdk/LibMdk.kt
@@ -0,0 +1,40 @@
+package com.mediadevkit.sdk
+
+object LibMdk {
+
+ init { System.loadLibrary("mdk-jni") }
+
+ init {
+ setGlobalOptionString("subtitle.fonts.file", "assets://fonts/font.ttf")
+ }
+
+ @JvmStatic external fun setGlobalOptionString(key: String, value: String)
+ @JvmStatic external fun setGlobalOptionInt(key: String, value: Int)
+
+ @JvmStatic external fun newGlobalRef(obj: Any): Long
+ @JvmStatic external fun deleteGlobalRef(ref: Long)
+
+ @JvmStatic external fun createPlayer(): Long
+
+ @JvmStatic external fun setState(handle: Long, state: Int)
+ @JvmStatic external fun getState(handle: Long): Int
+
+ @JvmStatic external fun setColorSpace(handle: Long, surface: Long, colorSpace: Int)
+
+ @JvmStatic external fun setMedia(handle: Long, url: String?)
+ @JvmStatic external fun release(handle: Long)
+
+ @JvmStatic external fun getPosition(handle: Long): Long
+ @JvmStatic external fun seek(handle: Long, position: Long)
+
+ @JvmStatic external fun setProperty(handle: Long, key: String, value: String)
+
+ @JvmStatic external fun updateNativeSurface(handle: Long, surface: Long, width: Int, height: Int, type: Int)
+
+ @JvmStatic external fun setListener(handle: Long, listener: KotlinPlayer.Listener)
+
+ @JvmStatic external fun getMediaInfo(handle: Long): MdkMediaInfo?
+
+
+
+}
\ No newline at end of file
diff --git a/sdk/src/main/java/com/mediadevkit/sdk/MediaInfo.kt b/sdk/src/main/java/com/mediadevkit/sdk/MediaInfo.kt
new file mode 100644
index 0000000..6be5e1c
--- /dev/null
+++ b/sdk/src/main/java/com/mediadevkit/sdk/MediaInfo.kt
@@ -0,0 +1,95 @@
+package com.mediadevkit.sdk
+
+data class MdkMediaInfo(
+ val startTime: Long,
+ val duration: Long,
+ val bitRate: Long,
+ val size: Long,
+ val format: String,
+ val streams: Int,
+ //todo: val chapters: List,
+ //todo: val metaData: MetaData,
+ val audio: List,
+ val video: List,
+ val subtitles: List,
+ //todo: val programInfo: List,
+)
+
+data class MdkAudioStream(
+ override val index: Int,
+ val startTime: Long,
+ val duration: Long,
+ val frames: Long,
+ val metaData: Map,
+ val codec: MdkAudioCodec,
+) : MdkStream
+
+data class MdkAudioCodec(
+ val codec: String,
+ val codecTag: Int,
+ //todo: val extraData: ByteArray,
+ val bitRate: Long,
+ val profile: Int,
+ val level: Int,
+ val frameRate: Float,
+ val isFloat: Boolean,
+ val isUnsigned: Boolean,
+ val isPlanar: Boolean,
+ val rawSampleSize: Int,
+ val channels: Int,
+ val sampleRate: Int,
+ val blockAlign: Int,
+ val frameSize: Int,
+)
+
+
+data class MdkVideoStream(
+ override val index: Int,
+ val startTime: Long,
+ val duration: Long,
+ val frames: Long,
+ val rotation: Int,
+ val metaData: Map,
+ val codec: MdkVideoCodec,
+ //todo: val imageData: ByteArray,
+ //todo: val imageSize: Int,
+) : MdkStream
+
+data class MdkVideoCodec(
+ val codec: String,
+ val codecTag: Int,
+ //todo: val extraData: ByteArray,
+ //todo: val extraDataSize: Int,
+ val bitRate: Long,
+ val profile: Int,
+ val level: Int,
+ val frameRate: Float,
+ val format: Int,
+ val formatName: String,
+ val width: Int,
+ val height: Int,
+ val bFrames: Int,
+ val par: Float,
+ val colorSpace: Int,
+ val dolbyVisionProfile: Int,
+)
+
+data class MdkSubtitle(
+ override val index: Int,
+ val startTime: Long,
+ val duration: Long,
+ val metaData: Map,
+ val codec: MdkSubtitleCodec,
+) : MdkStream
+
+sealed interface MdkStream { val index: Int }
+
+data class MdkSubtitleCodec(
+ val codec: String,
+ val codecTag: Int,
+ //todo: val extraData: ByteArray,
+ //todo: val extraDataSize: Int,
+ //todo: val width: Int,
+ //todo: val height: Int,
+)
+
diff --git a/sdk/src/main/java/com/mediadevkit/sdk/MediaStatus.kt b/sdk/src/main/java/com/mediadevkit/sdk/MediaStatus.kt
new file mode 100644
index 0000000..cc6a656
--- /dev/null
+++ b/sdk/src/main/java/com/mediadevkit/sdk/MediaStatus.kt
@@ -0,0 +1,37 @@
+package com.mediadevkit.sdk
+
+@JvmInline
+value class MediaStatus(private val flags: Int) {
+
+ private companion object {
+ private const val FLAG_NO_MEDIA = 0
+ private const val FLAG_UNLOADED = 1
+ private const val FLAG_LOADING = 1 shl 1
+ private const val FLAG_LOADED = 1 shl 2
+ private const val FLAG_PREPARED = 1 shl 8
+ private const val FLAG_STALLED = 1 shl 3
+ private const val FLAG_BUFFERING = 1 shl 4
+ private const val FLAG_BUFFERED = 1 shl 5
+ private const val FLAG_END = 1 shl 6
+ private const val FLAG_SEEKING = 1 shl 7
+ private const val FLAG_INVALID = 1 shl 31
+ }
+
+ val isNoMedia: Boolean get() = flags == FLAG_NO_MEDIA
+ val isUnloaded: Boolean get() = flags and FLAG_UNLOADED != 0
+ val isLoading: Boolean get() = flags and FLAG_LOADING != 0
+ val isLoaded: Boolean get() = flags and FLAG_LOADED != 0
+ val isPrepared: Boolean get() = flags and FLAG_PREPARED != 0
+ val isStalled: Boolean get() = flags and FLAG_STALLED != 0
+ val isBuffering: Boolean get() = flags and FLAG_BUFFERING != 0
+ val isBuffered: Boolean get() = flags and FLAG_BUFFERED != 0
+ val isEnd: Boolean get() = flags and FLAG_END != 0
+ val isSeeking: Boolean get() = flags and FLAG_SEEKING != 0
+ val isInvalid: Boolean get() = flags and FLAG_INVALID != 0
+
+ override fun toString(): String {
+ return "MediaStatus(flags=$flags, isNoMedia=$isNoMedia, isUnloaded=$isUnloaded, isLoading=$isLoading, isLoaded=$isLoaded, isPrepared=$isPrepared, isStalled=$isStalled, isBuffering=$isBuffering, isBuffered=$isBuffered, isEnd=$isEnd, isSeeking=$isSeeking, isInvalid=$isInvalid)"
+ }
+
+
+}
\ No newline at end of file
diff --git a/sdk/src/main/java/com/mediadevkit/sdk/MediaType.kt b/sdk/src/main/java/com/mediadevkit/sdk/MediaType.kt
new file mode 100644
index 0000000..0284b63
--- /dev/null
+++ b/sdk/src/main/java/com/mediadevkit/sdk/MediaType.kt
@@ -0,0 +1,3 @@
+package com.mediadevkit.sdk
+
+enum class MediaType { Unknown, Audio, Video, Subtitles }
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
deleted file mode 100644
index d0b9a62..0000000
--- a/settings.gradle
+++ /dev/null
@@ -1,3 +0,0 @@
-include ':sdk'
-include ':app'
-rootProject.name = "MDKPlayer"
\ No newline at end of file
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 0000000..55c99fc
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,34 @@
+rootProject.name = "MDKPlayer"
+
+enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
+
+pluginManagement {
+ repositories {
+ google {
+ mavenContent {
+ includeGroupAndSubgroups("androidx")
+ includeGroupAndSubgroups("com.android")
+ includeGroupAndSubgroups("com.google")
+ }
+ }
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+
+dependencyResolutionManagement {
+ repositories {
+ google {
+ mavenContent {
+ includeGroupAndSubgroups("androidx")
+ includeGroupAndSubgroups("com.android")
+ includeGroupAndSubgroups("com.google")
+ }
+ }
+ maven("https://jogamp.org/deployment/maven/")
+ mavenCentral()
+ }
+}
+
+include(":sdk")
+include(":app")
\ No newline at end of file