diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 000000000..e83a6039c
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "ndk-modules/ovkmplayer/ffmpeg-android-builder"]
+ path = ndk-modules/ovkmplayer/builder
+ url = https://github.com/tinelix/ffmpeg-android-builder
diff --git a/README.md b/README.md
index 235ae2072..51b28e8f0 100644
--- a/README.md
+++ b/README.md
@@ -13,54 +13,67 @@ We will be happy to accept your bugreports [in our bug-tracker](https://github.c
## Download APK
* **via F-Droid**
+ * **[repo.openvk.uk](https://repo.openvk.uk/repo/)** (much faster, mirror ~~[without TLS](http://repo.openvk.co/repo/)~~ not paid)
* [f-droid.org](https://f-droid.org/packages/uk.openvk.android.legacy/)
* [izzysoft.de](https://apt.izzysoft.de/fdroid/index/apk/uk.openvk.android.legacy)
- * [tinelix.ru](https://fdroid.tinelix.ru)
* **via [Telegram channel](https://t.me/+nPLHBZqAsFlhYmIy)**
* **via [Releases page](https://github.com/openvk/mobile-android-legacy/releases/latest)**
-* **via [NashStore](https://store.nashstore.ru/store/637cc36cfb3ed38835524503)** _(for Russian phones kinda π)_
+* **via [NashStore](https://store.nashstore.ru/store/637cc36cfb3ed38835524503)** _(why not?)_
* **via [Trashbox](https://trashbox.ru/topics/164477/openvk-legacy)**
* **via [4PDA](https://4pda.to/forum/index.php?showtopic=1057695)**
## Building
-We recommend using [Android Studio 3.1.2](https://developer.android.com/studio/archive) and Java 7 for perfect support of libraries developed for Android 2.1 Eclair and above.
+We recommend opening the project in [Android Studio 3.1.2](https://developer.android.com/studio/archive) along with Java 7 already installed for perfect support of libraries developed for Android 2.1 Eclair and above.
+
+To provide support for non-native codecs (Theora, VP8, Opus), **FFmpeg v. 2.2.4** is used.
+
+To compile them you need:
++ **GNU/Linux distro or WSL2** \
+ Yeah, it is still possible to build libraries on Linux/WSL2, perhaps an assembly will be added to Windows/Cygwin and macOS.
+
+ Tested on Debian 8.11.0, can be built in the latest distributions.
++ **[Android NDK r8e](http://web.archive.org/web/20130501232214/http://developer.android.com/tools/sdk/ndk/index.html) and [Android NDK r10e](https://github.com/android/ndk/wiki/Unsupported-Downloads#r10e)** \
+ If already there, you need to specify the path to your NDK via the `ANDROID_NDK_R8E` variable.
+
+ Android NDK `r8e` is highly recommended for providing FFmpeg support in Android 2.2 and below.
+
+ Also, in the project settings, specify the path to Android NDK r10e.
++ **Installed dependencies** \
+ See packages listing for [Ubuntu/Debian/Linux Mint](https://trac.ffmpeg.org/wiki/CompilationGuide/Ubuntu) or [CentOS/Fedora](https://trac.ffmpeg.org/wiki/CompilationGuide/Centos).
+
++ **Scripts that build FFmpeg from source** \
+ Run the command inside the OpenVK Legacy repository in terminal:
+ ```sh
+ chmod +x ./build-ffmpeg.sh
+ ANDROID_NDK_R8E=[path/to/ndk-r8e] ANDROID_NDK_R10E=[path/to/ndk-r10e] ./build-ffmpeg.sh
+ ```
+
+ The source codes of the FFmpeg libraries, as well as the code of builder for Android, are located in the `builder` submodule of the [`./ndk-modules/ovkmplayer` directory](https://github.com/openvk/mobile-android-legacy/tree/main/ndk-modules/ovkmplayer).
**ATTENTION!** After an `java.util.zip.ZipException: invalid entry compressed size (expected [m] but got [n] bytes)` error occurs in the `:[package_name]:mockableAndroidJar` task when using Android SDK Build-tools 28 and higher, be sure to clean the project.
-## Used App Components
-**Most compatible app components, including libraries, are guaranteed to work with Android 2.1 and above.**
-
-You may also find them useful for developing applications that support very old Android versions, despite security and stability issues in current Android versions.
-
-#### Libraries
-1. **[Android Support Library v24 for 1.6+](https://developer.android.com/topic/libraries/support-library)** (Apache License 2.0)
-2. **[HttpUrlWrapper](https://github.com/tinelix/httpurlwrapper)** (Apache License 2.0)
-3. **[PhotoView 1.2.5](https://github.com/Baseflow/PhotoView/tree/v1.2.5)** (Apache License 2.0)
-4. **[SlidingMenu with Android 10+ patch](https://github.com/tinelix/SlidingMenu)** (Apache License 2.0)
-5. **[OkHttp 3.8.0](https://square.github.io/okhttp/)** (Apache License 2.0)
-6. **[Twemojicon (Emojicon with Twemoji pack)](https://github.com/tinelix/twemojicon)** (Apache License 2.0)
-7. **[Retro-ActionBar](https://github.com/tinelix/retro-actionbar)** (Apache License 2.0)
-8. **[Retro-PopupMenu](https://github.com/tinelix/retro-popupmenu)** (Apache License 2.0)
-9. **[SystemBarTint](https://github.com/jgilfelt/SystemBarTint)** (Apache License 2.0)
-10. **[SwipeRefreshLayout Mod with Pull-to-Refresh](https://github.com/xyxyLiu/SwipeRefreshLayout)** (Apache License 2.0)
-11. **[android-i18n-plurals](https://github.com/populov/android-i18n-plurals)** (X11 License)
-12. **[Application Crash Reports 4.6.0](https://github.com/ACRA/acra/tree/acra-4.6.0)** (Apache License 2.0) \
- _About our usage of ACRA in application see [issue #153](https://github.com/openvk/mobile-android-legacy/issues/153)._
-14. **[Universal Image Loader](https://github.com/nostra13/Android-Universal-Image-Loader/tree/v1.9.5)** (Apache License 2.0)
-15. **[NineOldAndroids](https://github.com/JakeWharton/NineOldAndroids)** (Apache License 2.0)
-16. **[Apmem FlowLayout 1.8](https://github.com/ApmeM/android-flowlayout/tree/java-flowlayout-1.8)** (Apache License 2.0)
-
-#### Design
-1. **VK 3.x original resources** \
- Author: [Gregory Klyushnikov](https://grishka.me)
-2. **VK3-based themes:** Gray, Black
-3. [**Holo Design Language**](https://web.archive.org/web/20130217132335/http://developer.android.com/design/index.html)
+## Used Libraries
+1. [Android Support Library v24 for 1.6+](https://developer.android.com/topic/libraries/support-library) (Apache License 2.0)
+2. [HttpUrlWrapper](https://github.com/tinelix/httpurlwrapper) (Apache License 2.0)
+3. [PhotoView 1.2.5](https://github.com/Baseflow/PhotoView/tree/v1.2.5) (Apache License 2.0)
+4. [SlidingMenu with Android 10+ patch](https://github.com/tinelix/SlidingMenu) (Apache License 2.0)
+5. [OkHttp 3.8.0](https://square.github.io/okhttp/) (Apache License 2.0)
+6. [Twemojicon (Emojicon with Twemoji pack)](https://github.com/tinelix/twemojicon) (Apache License 2.0)
+7. [FFmpeg 2.2.4](https://github.com/tinelix/ffmpeg-android-builder/tree/main/ffmpeg-2.2.4) with [builder](https://github.com/tinelix/ffmpeg-android-builder) (LGPLv3.0)
+8. [Retro-ActionBar](https://github.com/tinelix/retro-actionbar) (Apache License 2.0)
+9. [Retro-PopupMenu](https://github.com/tinelix/retro-popupmenu) (Apache License 2.0)
+10. [SystemBarTint](https://github.com/jgilfelt/SystemBarTint) (Apache License 2.0)
+11. [SwipeRefreshLayout Mod with classic PTR header](https://github.com/xyxyLiu/SwipeRefreshLayout) (Apache License 2.0)
+12. [android-i18n-plurals](https://github.com/populov/android-i18n-plurals) (X11 License)
+13. [Application Crash Reports 4.6.0](https://github.com/ACRA/acra/tree/acra-4.6.0) (Apache License 2.0)
+14. [Universal Image Loader](https://github.com/nostra13/Android-Universal-Image-Loader/tree/v1.9.5) (Apache License 2.0)
+15. [NineOldAndroids animation API](https://github.com/JakeWharton/NineOldAndroids) (Apache License 2.0)
## OpenVK Legacy License
[GNU Affero GPL v3.0](COPYING) or later version.
## Links
-[OpenVK API docs](https://docs.openvk.su/openvk_engine/en/api/description/)\
+[OpenVK API docs](https://docs.openvk.su/openvk_engine/api/description/)\
[OpenVK Mobile](https://openvk.uk/app)
diff --git a/README_RU.md b/README_RU.md
index fe65c72df..fba6551bc 100644
--- a/README_RU.md
+++ b/README_RU.md
@@ -13,9 +13,9 @@ _Π Π°Π±ΠΎΡΠ°Π΅Ρ Π½Π° OpenVK API._
## Π‘ΠΊΠ°ΡΠ°ΡΡ APK
* **ΡΠ΅ΡΠ΅Π· F-Droid**
+ * **[repo.openvk.uk](https://repo.openvk.uk/repo/)** (Π½Π°ΠΌΠ½ΠΎΠ³ΠΎ Π±ΡΡΡΡΠ΅Π΅, Π·Π΅ΡΠΊΠ°Π»ΠΎ ~~[Π±Π΅Π· TLS](http://repo.openvk.co/repo/)~~ Π½Π΅ ΠΎΠΏΠ»Π°ΡΠ΅Π½ΠΎ)
* [f-droid.org](https://f-droid.org/packages/uk.openvk.android.legacy/)
* [izzysoft.de](https://apt.izzysoft.de/fdroid/index/apk/uk.openvk.android.legacy)
- * [tinelix.ru](https://repo.tinelix.ru)
* **ΡΠ΅ΡΠ΅Π· [Telegram-ΠΊΠ°Π½Π°Π»](https://t.me/+nPLHBZqAsFlhYmIy)**
* **ΡΠ΅ΡΠ΅Π· [ΡΡΡΠ°Π½ΠΈΡΡ ΡΠ΅Π»ΠΈΠ·ΠΎΠ²](https://github.com/openvk/mobile-android-legacy/releases/latest)**
* **ΡΠ΅ΡΠ΅Π· [NashStore](https://store.nashstore.ru/store/637cc36cfb3ed38835524503)** _(Π° ΠΏΠΎΡΠ΅ΠΌΡ Π±Ρ ΠΈ Π½Π΅Ρ?)_
@@ -23,44 +23,58 @@ _Π Π°Π±ΠΎΡΠ°Π΅Ρ Π½Π° OpenVK API._
* **ΡΠ΅ΡΠ΅Π· [4PDA](https://4pda.to/forum/index.php?showtopic=1057695)**
## Π‘Π±ΠΎΡΠΊΠ°
-ΠΡ ΡΠΎΠ²Π΅ΡΡΠ΅ΠΌ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ [Android Studio 3.1.2](https://developer.android.com/studio/archive) Π²ΠΌΠ΅ΡΡΠ΅ Ρ Java 7 Π΄Π»Ρ ΠΈΠ΄Π΅Π°Π»ΡΠ½ΠΎΠΉ ΠΏΠΎΠ΄Π΄Π΅ΡΠΆΠΊΠΈ Π±ΠΈΠ±Π»ΠΈΠΎΡΠ΅ΠΊ, ΡΠ°Π·ΡΠ°Π±ΠΎΡΠ°Π½Π½ΡΠ΅ Π΄Π»Ρ Android 2.1 Eclair ΠΈ Π²ΡΡΠ΅.
+ΠΡ ΡΠΎΠ²Π΅ΡΡΠ΅ΠΌ ΠΎΡΠΊΡΡΠ²Π°ΡΡ ΠΏΡΠΎΠ΅ΠΊΡ Π² [Android Studio 3.1.2](https://developer.android.com/studio/archive) Π²ΠΌΠ΅ΡΡΠ΅ Ρ ΡΠΆΠ΅ ΡΡΡΠ°Π½ΠΎΠ²Π»Π΅Π½Π½ΡΠΌ Java 7 Π΄Π»Ρ ΠΈΠ΄Π΅Π°Π»ΡΠ½ΠΎΠΉ ΠΏΠΎΠ΄Π΄Π΅ΡΠΆΠΊΠΈ Π±ΠΈΠ±Π»ΠΈΠΎΡΠ΅ΠΊ, ΡΠ°Π·ΡΠ°Π±ΠΎΡΠ°Π½Π½ΡΠ΅ Π΄Π»Ρ Android 2.1 Eclair ΠΈ Π²ΡΡΠ΅.
+
+ΠΠ»Ρ ΠΎΠ±Π΅ΡΠΏΠ΅ΡΠ΅Π½ΠΈΡ ΠΏΠΎΠ΄Π΄Π΅ΡΠΆΠΊΠΈ Π½Π΅Π½Π°ΡΠΈΠ²Π½ΡΡ
ΠΊΠΎΠ΄Π΅ΠΊΠΎΠ² (Theora, VP8, Opus) ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΡΡΡ Π²Π΅ΡΡΠΈΡ **FFmpeg 2.2.4**.
+
+Π§ΡΠΎΠ±Ρ ΡΠΎΠ±ΡΠ°ΡΡ ΠΈΡ
, Π²Π°ΠΌ ΠΏΠΎΠ½Π°Π΄ΠΎΠ±ΠΈΡΡΡ:
++ **ΠΠΈΡΡΡΠΈΠ±ΡΡΠΈΠ² GNU/Linux ΠΈΠ»ΠΈ WSL2** \
+ ΠΠ°, Π½Π° Π΄Π°Π½Π½ΡΠΉ ΠΌΠΎΠΌΠ΅Π½Ρ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½Π° ΡΠ±ΠΎΡΠΊΠ° Π±ΠΈΠ±Π»ΠΈΠΎΡΠ΅ΠΊ Π½Π° Linux/WSL2, Π½Π΅ ΠΈΡΠΊΠ»ΡΡΠ°Π΅ΠΌ Π΅Ρ Π΄ΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΡ ΠΏΠΎΠ΄ Windows/Cygwin ΠΈ macOS.
+
+ Π‘Π±ΠΎΡΠΊΠ° ΠΏΡΠΎΠ²Π΅ΡΠ΅Π½Π° Π½Π° Debian 8.11.0, ΠΌΠΎΠΆΠ΅Ρ Π±ΡΡΡ ΡΠ°ΠΊΠΆΠ΅ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½Π° Π² ΠΏΠΎΡΠ»Π΅Π΄Π½ΠΈΡ
Π²Π΅ΡΡΠΈΡΡ
Π΄ΠΈΡΡΡΠΈΠ±ΡΡΠΈΠ²ΠΎΠ².
++ **[Android NDK r8e](http://web.archive.org/web/20130501232214/http://developer.android.com/tools/sdk/ndk/index.html) ΠΈ [Android NDK r10e](https://github.com/android/ndk/wiki/Unsupported-Downloads#r10e)** \
+ ΠΡΠ»ΠΈ Ρ Π²Π°Ρ ΡΠΆΠ΅ Π΅ΡΡΡ, ΡΠΎ Π²Π°ΠΌ Π½ΡΠΆΠ½ΠΎ ΡΠΊΠ°Π·Π°ΡΡ ΠΏΡΡΡ ΠΊ NDK ΡΠ΅ΡΠ΅Π· ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΡΡ `ANDROID_NDK_HOME`.
+
+ Android NDK `r8e` Π½Π°ΡΡΠΎΡΡΠ΅Π»ΡΠ½ΠΎ ΡΠ΅ΠΊΠΎΠΌΠ΅Π½Π΄ΡΠ΅ΡΡΡ Π΄Π»Ρ ΠΏΡΠ΅Π΄ΠΎΡΡΠ°Π²Π»Π΅Π½ΠΈΡ ΠΏΠΎΠ΄Π΄Π΅ΡΠΆΠΊΠΈ FFmpeg Π² Android 2.2 ΠΈ Π½ΠΈΠΆΠ΅.
+
+ Π’Π°ΠΊΠΆΠ΅ Π² Π½Π°ΡΡΡΠΎΠΉΠΊΠ°Ρ
ΠΏΡΠΎΠ΅ΠΊΡΠ° ΡΠΊΠ°ΠΆΠΈΡΠ΅ ΠΏΡΡΡ ΠΊ Android NDK r10e.
++ **Π£ΡΡΠ°Π½ΠΎΠ²Π»Π΅Π½Π½ΡΠ΅ Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠΈ** \
+ ΠΠΎΡΠΌΠΎΡΡΠΈΡΠ΅ ΡΠΏΠΈΡΠΎΠΊ ΠΏΠ°ΠΊΠ΅ΡΠΎΠ² Π΄Π»Ρ [Ubuntu/Debian/Linux Mint](https://trac.ffmpeg.org/wiki/CompilationGuide/Ubuntu) ΠΈΠ»ΠΈ [CentOS/Fedora](https://trac.ffmpeg.org/wiki/CompilationGuide/Centos)
+
++ **Π‘ΠΊΡΠΈΠΏΡΡ, ΡΠΎΠ±ΠΈΡΠ°ΡΡΠΈΠ΅ FFmpeg ΠΈΠ· ΠΈΡΡ
ΠΎΠ΄Π½ΡΡ
ΠΊΠΎΠ΄ΠΎΠ²** \
+ ΠΠ°ΠΏΡΡΡΠΈΡΠ΅ ΠΊΠΎΠΌΠ°Π½Π΄Ρ Π²Π½ΡΡΡΠΈ ΡΠ΅ΠΏΠΎΠ·ΠΈΡΠΎΡΠΈΡ OpenVK Legacy Π² ΡΠ΅ΡΠΌΠΈΠ½Π°Π»Π΅:
+ ```sh
+ chmod +x ./build-ffmpeg.sh
+ ANDROID_NDK_R8E=[ΠΏΡΡΡ/ΠΊ/ndk-r8e] ANDROID_NDK_R10E=[ΠΏΡΡΡ/ΠΊ/ndk-r10e] ./build-ffmpeg.sh
+ ```
+
+ ΠΡΡ
ΠΎΠ΄Π½ΡΠ΅ ΠΊΠΎΠ΄Ρ Π±ΠΈΠ±Π»ΠΈΠΎΡΠ΅ΠΊ FFmpeg, Π° ΡΠ°ΠΊΠΆΠ΅ ΠΊΠΎΠ΄ ΡΠ±ΠΎΡΡΠΈΠΊΠ° ΠΏΠΎΠ΄ Android Π½Π°Ρ
ΠΎΠ΄ΡΡΡΡ Π² ΠΏΠΎΠ΄ΠΌΠΎΠ΄ΡΠ»Π΅ `builder` [Π΄ΠΈΡΠ΅ΠΊΡΠΎΡΠΈΠΈ `./ndk-modules/ovkmplayer`](https://github.com/openvk/mobile-android-legacy/tree/main/ndk-modules/ovkmplayer).
**ΠΠΠΠΠΠΠΠ!** ΠΠΎΡΠ»Π΅ Π²ΠΎΠ·Π½ΠΈΠΊΠ½ΠΎΠ²Π΅Π½ΠΈΡ ΠΎΡΠΈΠ±ΠΊΠΈ `java.util.zip.ZipException: invalid entry compressed size (expected [m] but got [n] bytes)` Π² Π·Π°Π΄Π°ΡΠ΅ `:[package_name]:mockableAndroidJar`, ΠΏΡΠΈ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠΈ Android SDK Build-tools 28 ΠΈ Π²ΡΡΠ΅ Π½Π΅ΠΎΠ±Ρ
ΠΎΠ΄ΠΈΠΌΠΎ ΠΎΡΠΈΡΡΠΈΡΡ ΠΏΡΠΎΠ΅ΠΊΡ (Clean Project).
-## ΠΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΠΌΡΠ΅ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΡ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ
-**ΠΠΎΠ»ΡΡΠΈΠ½ΡΡΠ²ΠΎ ΡΠΎΠ²ΠΌΠ΅ΡΡΠΈΠΌΡΡ
ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠΎΠ² ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ, Π²ΠΊΠ»ΡΡΠ°Ρ Π±ΠΈΠ±Π»ΠΈΠΎΡΠ΅ΠΊΠΈ, Π³Π°ΡΠ°Π½ΡΠΈΡΠΎΠ²Π°Π½Π½ΠΎ ΡΠ°Π±ΠΎΡΠ°ΡΡ Π² Android 2.1 ΠΈ Π²ΡΡΠ΅.**
-
-ΠΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎ, ΠΎΠ½ΠΈ Π²Π°ΠΌ ΠΏΡΠΈΠ³ΠΎΠ΄ΡΡΡΡ Π΄Π»Ρ ΡΠ°Π·ΡΠ°Π±ΠΎΡΠΊΠΈ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΉ Ρ ΠΏΠΎΠ΄Π΄Π΅ΡΠΆΠΊΠΎΠΉ ΠΎΡΠ΅Π½Ρ ΡΡΠ°ΡΡΡ
Π²Π΅ΡΡΠΈΠΉ Android, Π½Π΅ΡΠΌΠΎΡΡΡ Π½Π° ΠΏΡΠΎΠ±Π»Π΅ΠΌΡ Ρ Π±Π΅Π·ΠΎΠΏΠ°ΡΠ½ΠΎΡΡΡΡ ΠΈ ΡΡΠ°Π±ΠΈΠ»ΡΠ½ΠΎΡΡΡΡ Π² ΡΠ²Π΅ΠΆΠΈΡ
Π²Π΅ΡΡΠΈΡΡ
Android.
-
-#### ΠΠΈΠ±Π»ΠΈΠΎΡΠ΅ΠΊΠΈ
-
-1. **[Android Support Library v24](https://developer.android.com/topic/libraries/support-library)** (Apache License 2.0)
-2. **[HttpUrlWrapper](https://github.com/tinelix/httpurlwrapper)** (Apache License 2.0)
-3. **[PhotoView 1.2.5](https://github.com/Baseflow/PhotoView/tree/v1.2.5)** (Apache License 2.0)
-4. **[SlidingMenu Ρ ΠΏΠ°ΡΡΠ΅ΠΌ Π΄Π»Ρ Android 10+](https://github.com/tinelix/SlidingMenu)** (Apache License 2.0)
-5. **[OkHttp 3.8.0](https://square.github.io/okhttp/)** (Apache License 2.0)
-6. **[Twemojicon](https://github.com/tinelix/twemoji/tree/1.2)** (Apache License 2.0)
-8. **[Retro-ActionBar](https://github.com/tinelix/retro-actionbar)** (Apache License 2.0)
-9. **[Retro-PopupMenu](https://github.com/tinelix/retro-popupmenu)** (Apache License 2.0)
-10. **[SystemBarTint](https://github.com/jgilfelt/SystemBarTint)** (Apache License 2.0)
-11. **[ΠΠΎΠ΄ΠΈΡΠΈΠΊΠ°ΡΠΈΡ SwipeRefreshLayout Ρ Pull-to-Refresh](https://github.com/xyxyLiu/SwipeRefreshLayout)** (Apache License 2.0)
-12. **[android-i18n-plurals](https://github.com/populov/android-i18n-plurals)** (X11 License)
-13. **[Application Crash Reports 4.6.0](https://github.com/ACRA/acra/tree/acra-4.6.0)** (Apache License 2.0) \
- _ΠΠΎ ΠΏΠΎΠ²ΠΎΠ΄Ρ ΠΏΡΠΈΠΌΠ΅Π½Π΅Π½ΠΈΡ ACRA Π² ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΈ ΡΠΌΠΎΡΡΠΈΡΠ΅ [issue β153](https://github.com/openvk/mobile-android-legacy/issues/153)._
-15. **[Universal Image Loader](https://github.com/nostra13/Android-Universal-Image-Loader/tree/v1.9.5)** (Apache License 2.0)
-16. **[NineOldAndroids animation API](https://github.com/JakeWharton/NineOldAndroids)** (Apache License 2.0)
-
-#### ΠΡΠΎΡΠΌΠ»Π΅Π½ΠΈΠ΅
-1. **ΠΡΠΈΠ³ΠΈΠ½Π°Π»ΡΠ½ΡΠ΅ ΡΠ΅ΡΡΡΡΡ ΠΠΠΎΠ½ΡΠ°ΠΊΡΠ΅ 3.x** \
- ΠΠ²ΡΠΎΡ: [ΠΡΠΈΠ³ΠΎΡΠΈΠΉ ΠΠ»ΡΡΠ½ΠΈΠΊΠΎΠ²](https://grishka.me)
-2. **Π’Π΅ΠΌΡ ΠΎΡΠΎΡΠΌΠ»Π΅Π½ΠΈΡ, ΠΎΡΠ½ΠΎΠ²Π°Π½Π½ΡΠ΅ Π½Π° ΠΠ3:** "Π‘Π΅ΡΠ°Ρ" ΠΈ "Π§Π΅ΡΠ½Π°Ρ"
-3. [**Π―Π·ΡΠΊ Π΄ΠΈΠ·Π°ΠΉΠ½Π° Holo**](https://web.archive.org/web/20130217132335/http://developer.android.com/design/index.html)
+## ΠΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΠΌΡΠ΅ Π±ΠΈΠ±Π»ΠΈΠΎΡΠ΅ΠΊΠΈ
+1. [Android Support Library v24 for 1.6+](https://developer.android.com/topic/libraries/support-library) (Apache License 2.0)
+2. [HttpUrlWrapper](https://github.com/tinelix/httpurlwrapper) (Apache License 2.0)
+3. [PhotoView 1.2.5](https://github.com/Baseflow/PhotoView/tree/v1.2.5) (Apache License 2.0)
+4. [SlidingMenu with Android 10+ patch](https://github.com/tinelix/SlidingMenu) (Apache License 2.0)
+5. [OkHttp 3.8.0](https://square.github.io/okhttp/) (Apache License 2.0)
+6. [Twemojicon (Emojicon Ρ ΠΏΠ°ΠΊΠ΅ΡΠΎΠΌ Twemoji)](https://github.com/rockerhieu/emojicon/tree/1.2) (Apache License 2.0)
+7. [FFmpeg 2.2.4](https://github.com/tinelix/ffmpeg-android-builder/tree/ffmpeg-2.2.4) Π²ΠΌΠ΅ΡΡΠ΅ ΡΠΎ [ΡΠ±ΠΎΡΡΠΈΠΊΠΎΠΌ](https://github.com/tinelix/ffmpeg-android-builder/tree/42c67d80bc924c9709a7648e2d12f04ddf43b32b) (LGPLv3.0)
+8. [ijkplayer 0.8.2](https://github.com/bilibili/ijkplayer/tree/k0.6.2) (LGPL 2.1+)
+9. [Retro-ActionBar](https://github.com/tinelix/retro-actionbar) (Apache License 2.0)
+10. [Retro-PopupMenu](https://github.com/tinelix/retro-popupmenu) (Apache License 2.0)
+11. [SystemBarTint](https://github.com/jgilfelt/SystemBarTint) (Apache License 2.0)
+12. [ΠΠΎΠ΄ΠΈΡΠΈΠΊΠ°ΡΠΈΡ SwipeRefreshLayout Ρ ΠΊΠ»Π°ΡΡΠΈΡΠ΅ΡΠΊΠΈΠΌ PTR](https://github.com/xyxyLiu/SwipeRefreshLayout) (Apache License 2.0)
+13. [android-i18n-plurals](https://github.com/populov/android-i18n-plurals) (X11 License)
+14. [Application Crash Reports 4.6.0](https://github.com/ACRA/acra/tree/acra-4.6.0) (Apache License 2.0)
+15. [Universal Image Loader](https://github.com/nostra13/Android-Universal-Image-Loader/tree/v1.9.5) (Apache License 2.0)
+16. [NineOldAndroids animation API](https://github.com/JakeWharton/NineOldAndroids) (Apache License 2.0)
## ΠΠΈΡΠ΅Π½Π·ΠΈΡ OpenVK Legacy
[GNU Affero GPL v3.0](COPYING) ΠΈΠ»ΠΈ Π±ΠΎΠ»Π΅Π΅ ΠΏΠΎΠ·Π΄Π½Π΅ΠΉ Π²Π΅ΡΡΠΈΠΈ.
## Π‘ΡΡΠ»ΠΊΠΈ
-[ΠΠΎΠΊΡΠΌΠ΅Π½ΡΠ°ΡΠΈΡ ΠΏΠΎ OpenVK API](https://docs.openvk.su/openvk_engine/ru/api/description/)\
+[ΠΠΎΠΊΡΠΌΠ΅Π½ΡΠ°ΡΠΈΡ ΠΏΠΎ OpenVK API](https://docs.openvk.su/openvk_engine/api/description/)\
[OpenVK Mobile](https://openvk.uk/app)
diff --git a/app/app.iml b/app/app.iml
index 6f30c8d60..773c7a1f8 100644
--- a/app/app.iml
+++ b/app/app.iml
@@ -21,6 +21,11 @@
+
+
+
+
+
@@ -81,6 +86,7 @@
+
@@ -88,6 +94,7 @@
+
@@ -95,6 +102,7 @@
+
@@ -102,6 +110,7 @@
+
@@ -127,6 +136,15 @@
+
+
+
+
+
+
+
+
+
@@ -152,4 +170,3 @@
-
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index 620a4d94c..bb42034d7 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,4 +1,5 @@
apply plugin: 'com.android.application'
+import org.apache.tools.ant.taskdefs.condition.Os
def getGitHubCommit = {
try {
@@ -86,10 +87,16 @@ android {
release {
buildConfigField "String", "GITHUB_COMMIT", "\"${getGitHubCommit()}\""
buildConfigField "String", "SOURCE_CODE", "\"https://github.com/openvk/mobile-android-legacy\""
+ ndk {
+ abiFilters 'x86', 'armeabi', 'armeabi-v7a', 'arm64-v8a'
+ }
}
debug {
buildConfigField "String", "GITHUB_COMMIT", "\"${getGitHubCommit()}\""
buildConfigField "String", "SOURCE_CODE", "\"https://github.com/openvk/mobile-android-legacy\""
+ ndk {
+ abiFilters 'x86', 'armeabi', 'armeabi-v7a', 'arm64-v8a'
+ }
}
}
productFlavors {
@@ -130,6 +137,33 @@ android {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
+ task mPlayerBuild(type: Exec, description: 'Compile OpenVK Media Player via NDK') {
+ def ndkDir = android.ndkDirectory
+ def projDir = project.projectDir.toString()
+ println "Compiling OpenVK Media Player..."
+ println "Project Directory: ${projDir}"
+ if (Os.isFamily(Os.FAMILY_WINDOWS)) {
+ commandLine "$ndkDir\\ndk-build.cmd",
+ "NDK_PROJECT_PATH=$projDir/app/src/main",
+ "NDK_LIBS_OUT=$projDir/app/src/main/jniLibs",
+ "APP_BUILD_SCRIPT=$projDir/../ndk-modules/ovkmplayer/Android.mk",
+ "NDK_APPLICATION_MK=$projDir/../ndk-modules/ovkmplayer/Application.mk"
+ } else {
+ commandLine "$ndkDir/ndk-build.sh",
+ "NDK_PROJECT_PATH=$projDir/app/src/main",
+ "NDK_LIBS_OUT=$projDir/app/src/main/jniLibs",
+ "APP_BUILD_SCRIPT=$projDir/ndk-modules/ovkmplayer/Android.mk",
+ "NDK_APPLICATION_MK=$projDir/ndk-modules/ovkmplayer/Application.mk"
+ }
+ }
+ tasks.withType(JavaCompile) {
+ compileTask -> compileTask.dependsOn mPlayerBuild
+ }
+ externalNativeBuild {
+ ndkBuild {
+ path '../ndk-modules/ovkmplayer/Android.mk'
+ }
+ }
}
dependencies {
diff --git a/app/src/main/java/uk/openvk/android/legacy/core/activities/VideoPlayerActivity.java b/app/src/main/java/uk/openvk/android/legacy/core/activities/VideoPlayerActivity.java
index e421022df..75d14c442 100644
--- a/app/src/main/java/uk/openvk/android/legacy/core/activities/VideoPlayerActivity.java
+++ b/app/src/main/java/uk/openvk/android/legacy/core/activities/VideoPlayerActivity.java
@@ -202,7 +202,7 @@ private void playVideo() {
}
private void createMediaPlayer(String url) {
- OvkMediaPlayer mp = new OvkMediaPlayer(this);
+ mp = new OvkMediaPlayer(this);
try {
mp.setOnPreparedListener(new OvkMediaPlayer.OnPreparedListener() {
@Override
@@ -217,7 +217,7 @@ public void onPrepared(OvkMediaPlayer mp) {
vsh.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
mp.setDisplay(vsh);
mp.start();
- handler.postDelayed(hideCtrl, 5000);
+ //handler.postDelayed(hideCtrl, 5000);
new Handler(Looper.myLooper()).post(new Runnable() {
@Override
public void run() {
@@ -324,4 +324,10 @@ private void setThumbnail() {
public boolean isError() {
return isErr;
}
+
+ @Override
+ protected void onDestroy() {
+ mp.stop();
+ super.onDestroy();
+ }
}
diff --git a/app/src/main/java/uk/openvk/android/legacy/utils/media/OvkMediaPlayer.java b/app/src/main/java/uk/openvk/android/legacy/utils/media/OvkMediaPlayer.java
index 3a1a0425c..49acc1b0d 100644
--- a/app/src/main/java/uk/openvk/android/legacy/utils/media/OvkMediaPlayer.java
+++ b/app/src/main/java/uk/openvk/android/legacy/utils/media/OvkMediaPlayer.java
@@ -31,6 +31,7 @@
import android.graphics.RectF;
import android.media.AudioFormat;
import android.media.AudioManager;
+import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaPlayer;
import android.os.Build;
@@ -58,52 +59,44 @@
public class OvkMediaPlayer extends MediaPlayer {
private String MPLAY_TAG = "OVK-MPLAY";
- public static final int FFMPEG_ERROR_EOF = -541478725;
- public static final int STATE_STOPPED = 0;
- public static final int STATE_PLAYING = 1;
- public static final int STATE_PAUSED = 2;
public static final int MESSAGE_PREPARE = 10000;
public static final int MESSAGE_COMPLETE = 10001;
public static final int MESSAGE_ERROR = -10000;
- public static final int MESSAGE_AUDIO_DECODING = 100;
+
+ public static final int FFMPEG_COMMAND_OPEN_INPUT = 0x2000;
+ public static final int FFMPEG_COMMAND_FIND_STREAMS = 0x2001;
+ public static final int FFMPEG_COMMAND_OPEN_CODECS = 0x2002;
+ public static final int FFMPEG_PLAYBACK_ERROR = 0x7fff;
+ public static final int FFMPEG_PLAYBACK_STOPPED = 0x8000;
+ public static final int FFMPEG_PLAYBACK_PLAYING = 0x0801;
+ public static final int FFMPEG_PLAYBACK_PAUSED = 0x8002;
+
private boolean prepared_audio_buffer;
- private int audio_buffer_read_pos = 0;
- private int audio_buffer_write_pos = 0;
- private int audio_delay = 0;
- private int last_audio_delay_upd;
- private long frames_count;
private String dataSourceUrl;
private ArrayList tracks;
- private float current_frame_rate;
- private TimerTask getFpsTimerTask = new TimerTask() {
- @Override
- public void run() {
- if(tracks != null && tracks.size() > 0) {
- if(tracks.get(0) instanceof OvkVideoTrack) {
- OvkVideoTrack video_track = (OvkVideoTrack) tracks.get(0);
- current_frame_rate = video_track.frame_rate;
- }
- }
- }
- };
private SurfaceHolder holder;
private int minAudioBufferSize;
private int minVideoBufferSize;
- private byte[] audio_buffer;
- private byte[] video_buffer;
private OnPreparedListener onPreparedListener;
private OnErrorListener onErrorListener;
private OnCompletionListener onCompletionListener;
private Handler handler;
private AudioTrack audio_track;
- private final Object frameLocker = new Object();
+ private byte[] videoBuffer;
+
+ // C++ player native functions
private native void naInit();
private native String naShowLogo();
- private native Object naGenerateTrackInfo(int type);
- private native void naSetMinAudioBufferSize(int audioBufferSize);
- private native int naOpenFile(String filename);
- private native int naPlay();
private native void naSetDebugMode(boolean value);
+ private native int naGetPlaybackState();
+ private native int naOpenFile(String filename);
+ private native Object naGenerateTrackInfo(int type);
+ // private native void naSetMinAudioBufferSize(int audioBufferSize);
+ private native void naStartAudioDecoding();
+ private native void naStartVideoDecoding();
+ private native void naPlay();
+ private native void naPause();
+ private native void naStop();
public static interface OnPreparedListener {
public void onPrepared(OvkMediaPlayer mp);
@@ -119,12 +112,16 @@ public static interface OnCompletionListener {
@SuppressLint({"UnsafeDynamicallyLoadedCode", "SdCardPath"})
private static void loadLibrary(Context ctx, String name) {
- if(BuildConfig.DEBUG
- && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
- System.loadLibrary(String.format("%s", name));
- } else {
- // unsafe but changeable
- System.load(String.format("/data/data/%s/lib/lib%s.so", ctx.getPackageName(), name));
+ try {
+ if (BuildConfig.DEBUG
+ && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+ System.loadLibrary(String.format("%s", name));
+ } else {
+ // unsafe but changeable
+ System.load(String.format("/data/data/%s/lib/lib%s.so", ctx.getPackageName(), name));
+ }
+ } catch (Error | Exception e) {
+ e.printStackTrace();
}
}
@@ -132,8 +129,8 @@ public OvkMediaPlayer(Context ctx) {
loadLibrary(ctx, "ffmpeg");
loadLibrary(ctx, "ovkmplayer");
Log.v(MPLAY_TAG, naShowLogo());
- naSetDebugMode(true);
naInit();
+ naSetDebugMode(true);
handler = new Handler(Looper.myLooper()) {
@Override
public void handleMessage(Message msg) {
@@ -214,7 +211,7 @@ public void setDisplay(SurfaceHolder sh) {
}
@Override
- public void prepare() throws IOException, IllegalStateException {
+ public void prepare() throws IllegalStateException {
int result;
if((result = naOpenFile(dataSourceUrl)) < 0) {
Log.e(MPLAY_TAG, String.format("Can't open file: %s", dataSourceUrl));
@@ -224,7 +221,6 @@ public void prepare() throws IOException, IllegalStateException {
onErrorListener.onError(this, -1);
} else {
getMediaInfo();
- onPreparedListener.onPrepared(this);
}
}
@@ -234,24 +230,7 @@ public void prepareAsync() throws IllegalStateException {
@Override
public void run() {
int result;
- if((result = naOpenFile(dataSourceUrl)) < 0) {
- Log.e(MPLAY_TAG, String.format("Can't open file: %s", dataSourceUrl));
- Message msg = new Message();
- msg.what = MESSAGE_ERROR;
- msg.getData().putInt("error_code", result);
- handler.sendMessage(msg);
- //setPlaybackState(STATE_STOPPED);
- } else if(getMediaInfo() == null) {
- Log.e(MPLAY_TAG, String.format("Can't open file: %s", dataSourceUrl));
- Message msg = new Message();
- msg.what = MESSAGE_ERROR;
- msg.getData().putInt("error_code", -1);
- handler.sendMessage(msg);
- } else {
- Message msg = new Message();
- msg.what = MESSAGE_PREPARE;
- handler.sendMessage(msg);
- }
+ naOpenFile(dataSourceUrl);
}
}).start();
}
@@ -259,43 +238,22 @@ public void run() {
@Override
public void start() throws IllegalStateException {
if(tracks != null) {
+ naPlay();
Log.d(MPLAY_TAG, "Playing...");
- //setPlaybackState(STATE_PLAYING);
OvkAudioTrack audio_track = null;
OvkVideoTrack video_track = null;
for(int tracks_index = 0; tracks_index < tracks.size(); tracks_index++) {
if(tracks.get(tracks_index) instanceof OvkAudioTrack) {
audio_track = (OvkAudioTrack) tracks.get(tracks_index);
-// int ch_config = audio_track.channels == 2 ?
-// AudioFormat.CHANNEL_CONFIGURATION_STEREO : AudioFormat.CHANNEL_CONFIGURATION_MONO;
-// minAudioBufferSize =
-// AudioTrack.getMinBufferSize(
-// (int) audio_track.sample_rate,
-// ch_config,
-// AudioFormat.ENCODING_PCM_16BIT
-// );
-// naSetMinAudioBufferSize(minAudioBufferSize);
} else if(tracks.get(tracks_index) instanceof OvkVideoTrack) {
video_track = (OvkVideoTrack) tracks.get(tracks_index);
}
}
- final OvkVideoTrack finalVideoTrack = video_track;
final OvkAudioTrack finalAudioTrack = audio_track;
- new Thread(new Runnable() {
- @Override
- public void run() {
- if(finalAudioTrack != null || finalVideoTrack != null) {
- Log.d(MPLAY_TAG, "Decoding media file...");
- try {
- naPlay();
- } catch (OutOfMemoryError oom) {
- stop();
- }
- } else {
- Log.e(MPLAY_TAG, "A/V streams not found. Skipping...");
- }
- }
- }).start();
+ final OvkVideoTrack finalVideoTrack = video_track;
+ naStartAudioDecoding();
+
+ naStartVideoDecoding();
}
}
@@ -306,6 +264,7 @@ private void renderAudio(final byte[] buffer, final int length) {
Log.e(MPLAY_TAG, "Audio buffer is empty");
return;
}
+
if (!prepared_audio_buffer) {
for (int tracks_index = 0; tracks_index < tracks.size(); tracks_index++) {
if (tracks.get(tracks_index) instanceof OvkAudioTrack) {
@@ -321,11 +280,17 @@ private void renderAudio(final byte[] buffer, final int length) {
audio_track = new AudioTrack(AudioManager.STREAM_MUSIC, (int) track.sample_rate,
ch_config,
- AudioFormat.ENCODING_PCM_16BIT, buffer.length, AudioTrack.MODE_STREAM);
+ AudioFormat.ENCODING_PCM_16BIT, length, AudioTrack.MODE_STREAM);
+
+ minAudioBufferSize = AudioRecord.getMinBufferSize(
+ (int) (track.sample_rate),
+ ch_config,
+ AudioFormat.ENCODING_PCM_16BIT);
audio_track.play();
prepared_audio_buffer = true;
}
+
try {
audio_track.write(buffer, 0, length);
} catch (Exception ignored) {
@@ -337,68 +302,76 @@ private void completePlayback() {
}
private void renderVideo(final byte[] buffer, final int length) {
- new Thread(new Runnable() {
- @Override
- public void run() {
- Canvas c;
- OvkVideoTrack track = null;
- for (int tracks_index = 0; tracks_index < tracks.size(); tracks_index++) {
- if (tracks.get(tracks_index) instanceof OvkVideoTrack) {
- track = (OvkVideoTrack) tracks.get(tracks_index);
- }
- }
- if (track != null) {
- int frame_width = track.frame_size[0];
- int frame_height = track.frame_size[1];
- if (frame_width > 0 && frame_height > 0) {
- minVideoBufferSize = frame_width * frame_height * 4;
- try {
- // RGB_565 == 65K colours (16 bit)
- // RGB_8888 == 16.7M colours (24 bit w/ alpha ch.)
- int bpp = Build.VERSION.SDK_INT > 9 ? 16 : 24;
- Bitmap.Config bmp_config =
- bpp == 24 ? Bitmap.Config.RGB_565 : Bitmap.Config.ARGB_8888;
- Paint paint = new Paint();
- if(buffer != null && holder != null) {
- holder.setType(SurfaceHolder.SURFACE_TYPE_NORMAL);
- if((c = holder.lockCanvas()) == null) {
- Log.d(MPLAY_TAG, "Lock canvas failed");
- return;
- }
- ByteBuffer bbuf =
- ByteBuffer.allocateDirect(minVideoBufferSize);
- bbuf.rewind();
- for(int i = 0; i < buffer.length; i++) {
- bbuf.put(i, buffer[i]);
- }
- bbuf.rewind();
- Bitmap bmp = Bitmap.createBitmap(frame_width, frame_height, bmp_config);
- bmp.copyPixelsFromBuffer(bbuf);
- float aspect_ratio = (float) frame_width / (float) frame_height;
- int scaled_width = (int)(aspect_ratio * (c.getHeight()));
- c.drawBitmap(bmp,
- null,
- new RectF(
- ((c.getWidth() - scaled_width) / 2), 0,
- ((c.getWidth() - scaled_width) / 2) + scaled_width,
- c.getHeight()),
- null);
- holder.unlockCanvasAndPost(c);
- bmp.recycle();
- bbuf.clear();
- } else {
- Log.d(MPLAY_TAG, "Video frame buffer is null");
- }
- } catch (Exception ex) {
- ex.printStackTrace();
- } catch (OutOfMemoryError oom) {
- oom.printStackTrace();
- stop();
+ Canvas c;
+ videoBuffer = buffer;
+ OvkVideoTrack track = null;
+ for (int tracks_index = 0; tracks_index < tracks.size(); tracks_index++) {
+ if (tracks.get(tracks_index) instanceof OvkVideoTrack) {
+ track = (OvkVideoTrack) tracks.get(tracks_index);
+ }
+ }
+ if (track != null) {
+ int frame_width = track.frame_size[0];
+ int frame_height = track.frame_size[1];
+ if (frame_width > 0 && frame_height > 0) {
+ minVideoBufferSize = frame_width * frame_height * 4;
+ try {
+ // RGB_565 == 65K colours (16 bit)
+ // RGB_8888 == 16.7M colours (24 bit w/ alpha ch.)
+ int bpp = Build.VERSION.SDK_INT > 9 ? 16 : 24;
+ Bitmap.Config bmp_config =
+ bpp == 24 ? Bitmap.Config.RGB_565 : Bitmap.Config.ARGB_8888;
+ if(videoBuffer != null && holder != null) {
+ holder.setType(SurfaceHolder.SURFACE_TYPE_NORMAL);
+ if((c = holder.lockCanvas()) == null) {
+ Log.d(MPLAY_TAG, "Lock canvas failed");
+ return;
+ }
+ ByteBuffer bbuf =
+ ByteBuffer.allocateDirect(minVideoBufferSize);
+ bbuf.rewind();
+ for(int i = 0; i < videoBuffer.length; i++) {
+ bbuf.put(i, videoBuffer[i]);
}
+ bbuf.rewind();
+ Bitmap bmp = Bitmap.createBitmap(frame_width, frame_height, bmp_config);
+ bmp.copyPixelsFromBuffer(bbuf);
+ float aspect_ratio = (float) frame_width / (float) frame_height;
+ int scaled_width = (int)(aspect_ratio * (c.getHeight()));
+ videoBuffer = null;
+ c.drawBitmap(bmp,
+ null,
+ new RectF(
+ ((c.getWidth() - scaled_width) / 2), 0,
+ ((c.getWidth() - scaled_width) / 2) + scaled_width,
+ c.getHeight()),
+ null);
+ holder.unlockCanvasAndPost(c);
+ bmp.recycle();
+ bbuf.clear();
+ } else {
+ Log.d(MPLAY_TAG, "Video frame buffer is null");
}
+
+ Thread.sleep((long) (800 / track.frame_rate));
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ } catch (OutOfMemoryError oom) {
+ oom.printStackTrace();
+ stop();
}
}
- }).start();
+ }
+ }
+
+ @Override
+ public void stop() throws IllegalStateException {
+ //naStop();
+ }
+
+ @Override
+ public void pause() throws IllegalStateException {
+ //naPause();
}
@Override
@@ -408,8 +381,30 @@ public int getDuration() {
@Override
public boolean isPlaying() {
- //return (getPlaybackState() == STATE_PLAYING);
- return false;
+ return (getPlaybackState() == FFMPEG_PLAYBACK_PLAYING);
+ }
+
+ private int getPlaybackState() {
+ return naGetPlaybackState();
+ }
+
+ public void onResult(int cmdId, int resultCode) {
+ if(cmdId == FFMPEG_COMMAND_OPEN_CODECS) {
+ if(getMediaInfo() == null) {
+ Log.e(MPLAY_TAG, String.format("Can't open file: %s", dataSourceUrl));
+ Message msg = new Message();
+ msg.what = MESSAGE_ERROR;
+ msg.getData().putInt("error_code", -1);
+ handler.sendMessage(msg);
+ } else {
+ new Handler(Looper.getMainLooper()).post(new Runnable() {
+ @Override
+ public void run() {
+ onPreparedListener.onPrepared(OvkMediaPlayer.this);
+ }
+ });
+ }
+ }
}
public void setOnPreparedListener(OvkMediaPlayer.OnPreparedListener listener) {
diff --git a/build-ffmpeg.sh b/build-ffmpeg.sh
new file mode 100644
index 000000000..83ec4c697
--- /dev/null
+++ b/build-ffmpeg.sh
@@ -0,0 +1,38 @@
+#!/bin/bash
+#
+# FFmpeg 2.8.11 for Android automatic build script using builder by Tinelix
+#
+# NOTICE: FFmpeg and FFmpeg for Android builder by Tinelix licensed under LGPLv3 or later version.
+
+FFMPEG_BUILDER_REPO="https://github.com/tinelix/ffmpeg-android-builder"
+
+echo "OpenVK Legacy | Downloading from ${FFMPEG_BUILDER_REPO}..."
+git submodule init
+git submodule update
+cd ndk-modules/ovkmplayer
+cd builder
+git checkout origin/main
+chmod -R 0777 . && chmod +x ./build-android-2.8.11.sh
+
+echo "OpenVK Legacy | Starting FFmpeg builder..."
+
+ANDROID_NDK_HOME=${ANDROID_NDK_R10E} ./build-android-2.8.11.sh armv8a r10e
+ANDROID_NDK_HOME=${ANDROID_NDK_R8E} ./build-android-2.8.11.sh armv7 r8e
+ANDROID_NDK_HOME=${ANDROID_NDK_R8E} ./build-android-2.8.11.sh armv6 r8e
+ANDROID_NDK_HOME=${ANDROID_NDK_R8E} ./build-android-2.8.11.sh x86 r8e
+
+# Create directories in jniLibs and ovkmplayer
+mkdir -p ../../../app/src/main/jniLibs/armeabi
+mkdir -p ../../../app/src/main/jniLibs/armeabi-v7a
+mkdir -p ../../../app/src/main/jniLibs/arm64-v8a
+mkdir -p ../../../app/src/main/jniLibs/x86
+
+echo "OpenVK Legacy | Copying libraries to project..."
+echo;
+
+cp ./ffmpeg-2.8.11/android/arm64-v8a/libffmpeg.so ../../../app/src/main/jniLibs/arm64-v8a
+cp ./ffmpeg-2.8.11/android/armeabi/libffmpeg.so ../../../app/src/main/jniLibs/armeabi
+cp ./ffmpeg-2.8.11/android/armeabi-v7a/libffmpeg.so ../../../app/src/main/jniLibs/armeabi-v7a
+cp ./ffmpeg-2.8.11/android/x86/libffmpeg.so ../../../app/src/main/jniLibs/x86
+
+echo "OpenVK Legacy | Done!"
diff --git a/ndk-modules/ovkmplayer/Android.mk b/ndk-modules/ovkmplayer/Android.mk
new file mode 100644
index 000000000..ce75005b9
--- /dev/null
+++ b/ndk-modules/ovkmplayer/Android.mk
@@ -0,0 +1,58 @@
+# OPENVK LEGACY LICENSE NOTIFICATION
+#
+# This program is free software: you can redistribute it and/or modify it under the terms of
+# the GNU Affero General Public License as published by the Free Software Foundation, either
+# version 3 of the License, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License along with this
+# program. If not, see https://www.gnu.org/licenses/.
+#
+# Source code: https://github.com/openvk/mobile-android-legacy
+#
+# NOTICE: FFmpeg and FFmpeg custom builder for Android licensed under LGPLv3.0 or later version.
+
+LOCAL_PATH := $(call my-dir)
+PROJECT_PATH := $(call my-dir)/../..
+FFMPEG_VERSION = 2.8.11
+
+FFMPEG_PATH = $(call my-dir)/builder/ffmpeg-$(FFMPEG_VERSION)
+#declare the prebuilt library
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := ffmpeg-prebuilt
+LOCAL_SRC_FILES := $(PROJECT_PATH)/app/src/main/jniLibs/$(TARGET_ARCH_ABI)/libffmpeg.so
+LOCAL_EXPORT_C_INCLUDES := $(PROJECT_PATH)/ndk-modules/ovkmplayer/builder/ffmpeg-$(FFMPEG_VERSION)/android/$(TARGET_ARCH_ABI)/include
+LOCAL_EXPORT_LDLIBS := $(PROJECT_PATH)/app/src/main/jniLibs/$(TARGET_ARCH_ABI)/libffmpeg.so
+LOCAL_PRELINK_MODULE := true
+LOCAL_CFLAGS += -std=c++98
+LOCAL_LDFLAGS += -ljnigraphics
+
+include $(PREBUILT_SHARED_LIBRARY)
+
+#the andzop library
+include $(CLEAR_VARS)
+LOCAL_ALLOW_UNDEFINED_SYMBOLS=false
+LOCAL_MODULE := ovkmplayer
+LOCAL_SRC_FILES := ovkmplay.cpp \
+ utils/android.cpp \
+ utils/ffwrap.cpp \
+ decoders/audiodec.cpp \
+ decoders/videodec.cpp \
+ utils/pktqueue.cpp \
+ utils/decthread.cpp
+LOCAL_C_INCLUDES := $(PROJECT_PATH)/ndk-modules/ovkmplayer/builder/ffmpeg-$(FFMPEG_VERSION)/android/$(TARGET_ARCH_ABI)/include
+LOCAL_C_INCLUDES += $(PROJECT_PATH)/ndk-modules/ovkmplayer/builder/ffmpeg-$(FFMPEG_VERSION) \
+ $(PROJECT_PATH)/ndk-modules/ovkmplayer/utils \
+ $(PROJECT_PATH)/ndk-modules/ovkmplayer/decoders \
+ $(PROJECT_PATH)/ndk-modules/ovkmplayer/interfaces
+LOCAL_CFLAGS += -std=c++98
+LOCAL_CPP_FEATURES := exceptions
+LOCAL_SHARED_LIBRARIES := ffmpeg-prebuilt
+LOCAL_LDLIBS := -llog -lz -lm $(PROJECT_PATH)/app/src/main/jniLibs/$(TARGET_ARCH_ABI)/libffmpeg.so
+
+include $(BUILD_SHARED_LIBRARY)
+
diff --git a/ndk-modules/ovkmplayer/Application.mk b/ndk-modules/ovkmplayer/Application.mk
new file mode 100644
index 000000000..37403d3d5
--- /dev/null
+++ b/ndk-modules/ovkmplayer/Application.mk
@@ -0,0 +1,18 @@
+# OPENVK LEGACY LICENSE NOTIFICATION
+#
+# This program is free software: you can redistribute it and/or modify it under the terms of
+# the GNU Affero General Public License as published by the Free Software Foundation, either
+# version 3 of the License, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License along with this
+# program. If not, see https://www.gnu.org/licenses/.
+#
+# Source code: https://github.com/openvk/mobile-android-legacy
+#
+# NOTICE: FFmpeg and FFmpeg custom builder for Android licensed under LGPLv3.0 or later version.
+
+APP_ABI := armeabi armeabi-v7a arm64-v8a x86
+APP_PLATFORM := android-5
diff --git a/ndk-modules/ovkmplayer/builder b/ndk-modules/ovkmplayer/builder
new file mode 160000
index 000000000..3e2c05dd9
--- /dev/null
+++ b/ndk-modules/ovkmplayer/builder
@@ -0,0 +1 @@
+Subproject commit 3e2c05dd9579f6418a914d58c35428357c46da2c
diff --git a/ndk-modules/ovkmplayer/decoders/audiodec.cpp b/ndk-modules/ovkmplayer/decoders/audiodec.cpp
new file mode 100644
index 000000000..63e5c5d81
--- /dev/null
+++ b/ndk-modules/ovkmplayer/decoders/audiodec.cpp
@@ -0,0 +1,95 @@
+//
+// Created by tretdm on 20.04.2024.
+//
+
+#include "audiodec.h"
+
+#include <../utils/android.h>
+
+#include
+#include
+
+#define AV_MAX_AUDIO_FRAME_SIZE 192000;
+
+AVStream* gStream;
+
+AudioDecoder::AudioDecoder(AVFormatContext *pFormatCtx,
+ AVCodecContext *pCodecCtx,
+ AVStream* pStream,
+ int pStreamIndex,
+ IFFmpegWrapper *pInterface) {
+ gFormatCtx = pFormatCtx;
+ gCodecCtx = pCodecCtx;
+ gStream = pStream;
+ gStreamIndex = pStreamIndex;
+ gInterface = pInterface;
+}
+
+bool AudioDecoder::prepare() {
+ gBufferSize = AV_MAX_AUDIO_FRAME_SIZE;
+ gBuffer = (short*) av_malloc(gBufferSize);
+ gSwrCtx = swr_alloc();
+ gSwrCtx = swr_alloc_set_opts(
+ gSwrCtx, (int64_t) gCodecCtx->channel_layout, AV_SAMPLE_FMT_S16,
+ gCodecCtx->sample_rate, gCodecCtx->channel_layout,
+ gCodecCtx->sample_fmt, gCodecCtx->sample_rate, 0, NULL
+ );
+ swr_init(gSwrCtx);
+ return gBuffer != NULL;
+}
+
+static void *s_decodeInThread(void *arg) {
+ return ((AudioDecoder*) arg)->decodeInThread();
+}
+
+void *AudioDecoder::decodeInThread() {
+ int status, dataSize, len;
+ AVPacket avPkt;
+ AVFrame *pFrame = av_frame_alloc();
+
+ while(av_read_frame(gFormatCtx, &avPkt)>=0) {
+ // It is from the audio stream?
+ if(avPkt.stream_index == gStreamIndex) {
+ len = avcodec_decode_audio4(gStream->codec, pFrame, &status, &avPkt);
+ if(len < 0) {
+ break;
+ }
+ if (status) {
+ dataSize = av_samples_get_buffer_size(
+ NULL, gCodecCtx->channels, pFrame->nb_samples,
+ gCodecCtx->sample_fmt, 1
+ );
+ uint8_t* buffer = (uint8_t*)av_malloc(sizeof(uint8_t) * dataSize);
+ swr_convert(
+ gSwrCtx, &buffer, dataSize,
+ (const uint8_t **) pFrame->data,
+ pFrame->nb_samples
+ );
+ memcpy(gBuffer, buffer, dataSize);
+ av_free(buffer);
+ gInterface->onStreamDecoding((uint8_t*)gBuffer, dataSize, gStreamIndex);
+ }
+ }
+ // Free the packet that was allocated by av_read_frame
+ av_free_packet(&avPkt);
+ av_packet_unref(&avPkt);
+ }
+ av_free(pFrame);
+ stop();
+}
+
+bool AudioDecoder::start() {
+ decodeInThread();
+ return true;
+}
+
+bool AudioDecoder::stop() {
+ free(gBuffer);
+ swr_free(&gSwrCtx);
+ avcodec_close(gCodecCtx);
+ return true;
+}
+
+double AudioDecoder::getPacketTime(AVPacket avPkt) {
+ return (avPkt.dts - gStream->start_time) * av_q2d(gStream->time_base);
+}
diff --git a/ndk-modules/ovkmplayer/decoders/audiodec.h b/ndk-modules/ovkmplayer/decoders/audiodec.h
new file mode 100644
index 000000000..f18c21ab4
--- /dev/null
+++ b/ndk-modules/ovkmplayer/decoders/audiodec.h
@@ -0,0 +1,84 @@
+//
+// Created by tretdm on 20.04.2024.
+//
+
+#ifndef MOBILE_ANDROID_LEGACY_AUDIODEC_H
+#define MOBILE_ANDROID_LEGACY_AUDIODEC_H
+
+#include <../utils/pktqueue.h>
+#include <../interfaces/ffwrap.h>
+#include
+#include
+
+#define LOG_TAG "FFwrap"
+#define LOG_LEVEL 10
+#define LOGD(level, ...) if (level <= LOG_LEVEL) {__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__);}
+#define LOGI(level, ...) if (level <= LOG_LEVEL) {__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__);}
+#define LOGW(level, ...) if (level <= LOG_LEVEL) {__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__);}
+#define LOGE(level, ...) if (level <= LOG_LEVEL) {__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__);}
+
+typedef void (*DecoderHandler) (short*, int);
+
+// Non-standard 'stdint' implementation
+#pragma clang diagnostic push
+#pragma ide diagnostic ignored "OCDFAInspection"
+extern "C"{
+ #ifdef __cplusplus
+ #define __STDC_CONSTANT_MACROS
+ #ifdef _STDINT_H
+ #undef _STDINT_H
+ #endif
+ # include
+ #endif
+}
+#ifndef INT64_C
+#define INT64_C(c) (c ## LL)
+#define UINT64_C(c) (c ## ULL)
+#endif
+
+// FFmpeg implementation headers (using LGPLv3.0 model)
+extern "C" {
+ #define __STDC_CONSTANT_MACROS // workaround for compiler
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+}
+
+class AudioDecoder {
+ public:
+ AudioDecoder(AVFormatContext *pFormatCtx,
+ AVCodecContext *pCodecCtx,
+ AVStream* pStream,
+ int pStreamIndex,
+ IFFmpegWrapper *pInterface);
+ bool prepare();
+ bool process();
+ bool decode(void *ptr);
+ DecoderHandler onDecode;
+ int gBufferSize, gStreamIndex;
+ short* gBuffer;
+ bool gRunning;
+ AVFormatContext *gFormatCtx;
+ AVCodecContext *gCodecCtx;
+ IFFmpegWrapper *gInterface;
+ SwrContext *gSwrCtx;
+ bool start();
+ bool stop();
+ void* decodeInThread();
+ double getPacketTime(AVPacket avPkt);
+ private:
+ PacketQueue* gPktQueue;
+};
+
+
+
+#endif //MOBILE_ANDROID_LEGACY_AUDIODEC_H
diff --git a/ndk-modules/ovkmplayer/decoders/videodec.cpp b/ndk-modules/ovkmplayer/decoders/videodec.cpp
new file mode 100644
index 000000000..96657b85f
--- /dev/null
+++ b/ndk-modules/ovkmplayer/decoders/videodec.cpp
@@ -0,0 +1,117 @@
+//
+// Created by tretdm on 20.04.2024.
+//
+
+#include "videodec.h"
+
+VideoDecoder::VideoDecoder(AVFormatContext *pFormatCtx,
+ AVCodecContext *pCodecCtx,
+ AVStream* pStream,
+ int pStreamIndex,
+ IFFmpegWrapper *pInterface) {
+ gFormatCtx = pFormatCtx;
+ gCodecCtx = pCodecCtx;
+ gStream = pStream;
+ gStreamIndex = pStreamIndex;
+ gInterface = pInterface;
+}
+
+bool VideoDecoder::prepare() {
+ return true;
+}
+
+static void *s_decodeInThread(void *arg) {
+ return ((VideoDecoder*) arg)->decodeInThread();
+}
+
+void *VideoDecoder::decodeInThread() {
+ AVPacket avPkt;
+ int vWidth = gCodecCtx->width,
+ vHeight = gCodecCtx->height,
+ status, len,
+ dataSize = avpicture_get_size(AV_PIX_FMT_RGB32, vWidth, vHeight),
+ packetSize, tVideoFrames;
+ struct SwsContext *img_convert_ctx = NULL;
+
+ gBuffer = (short*) av_mallocz((size_t)dataSize);
+
+ while(av_read_frame(gFormatCtx, &avPkt)>=0) {
+ gFrame = avcodec_alloc_frame();
+ // It is from the video stream?
+ if(avPkt.stream_index == gStreamIndex) {
+ packetSize = avPkt.size;
+ struct SwsContext *img_convert_ctx = NULL;
+ avpicture_fill((AVPicture*) gFrame,
+ (const uint8_t*) gBuffer,
+ gCodecCtx->pix_fmt,
+ gCodecCtx->width,
+ gCodecCtx->height
+ );
+
+ avcodec_decode_video2(gCodecCtx, gFrame, &status, &avPkt);
+ if(!status || gFrame == NULL || packetSize == 0) {
+ tVideoFrames++;
+ continue;
+ }
+ AVPixelFormat pxf;
+
+ pxf = AV_PIX_FMT_BGR32;
+
+ convertYuv2Rgb(pxf, gFrame, dataSize);
+ tVideoFrames++;
+ gInterface->onStreamDecoding((uint8_t*)gBuffer, dataSize, gStreamIndex);
+ }
+ av_free(gFrame);
+ // Free the packet that was allocated by av_read_frame
+ av_free_packet(&avPkt);
+ av_packet_unref(&avPkt);
+ }
+
+ stop();
+}
+
+bool VideoDecoder::start() {
+ decodeInThread();
+ return true;
+}
+
+short* VideoDecoder::convertYuv2Rgb(AVPixelFormat pxf, AVFrame* frame, int length) {
+ AVFrame *frameRGB = av_frame_alloc();
+ AVPixelFormat output_pxf = pxf;
+
+ avpicture_fill((AVPicture *)frameRGB, (uint8_t*)gBuffer, output_pxf,
+ gCodecCtx->width, gCodecCtx->height);
+ const int width = gCodecCtx->width, height = gCodecCtx->height;
+ SwsContext* img_convert_ctx = sws_getContext(width, height,
+ gCodecCtx->pix_fmt,
+ width, height, output_pxf, SWS_BICUBIC,
+ NULL, NULL, NULL);
+
+
+ if(img_convert_ctx == NULL) {
+ LOGE(10, "[ERROR] Cannot initialize the conversion context!");
+ sws_freeContext(img_convert_ctx);
+ return NULL;
+ }
+
+ int ret = sws_scale(img_convert_ctx, (const uint8_t* const*)frame->data, frame->linesize, 0,
+ gCodecCtx->height, frameRGB->data, frameRGB->linesize);
+ if(frameRGB->data[0] == NULL) {
+ LOGE(10, "[ERROR] SWS_Scale failed");
+ }
+ av_free(frameRGB);
+ av_frame_unref(frameRGB);
+ sws_freeContext(img_convert_ctx);
+ return gBuffer;
+}
+
+bool VideoDecoder::stop() {
+ av_free(gFrame);
+ avcodec_close(gCodecCtx);
+ av_free(gBuffer);
+ return true;
+}
+
+double VideoDecoder::getPacketTime(AVPacket avPkt) {
+ return (avPkt.dts - gStream->start_time) * av_q2d(gStream->time_base);
+}
diff --git a/ndk-modules/ovkmplayer/decoders/videodec.h b/ndk-modules/ovkmplayer/decoders/videodec.h
new file mode 100644
index 000000000..c14e3a49c
--- /dev/null
+++ b/ndk-modules/ovkmplayer/decoders/videodec.h
@@ -0,0 +1,85 @@
+//
+// Created by tretdm on 20.04.2024.
+//
+
+#ifndef MOBILE_ANDROID_LEGACY_VIDEODEC_H
+#define MOBILE_ANDROID_LEGACY_VIDEODEC_H
+
+#include <../utils/pktqueue.h>
+
+#include
+
+#define LOG_TAG "FFwrap"
+#define LOG_LEVEL 10
+#define LOGD(level, ...) if (level <= LOG_LEVEL) {__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__);}
+#define LOGI(level, ...) if (level <= LOG_LEVEL) {__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__);}
+#define LOGW(level, ...) if (level <= LOG_LEVEL) {__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__);}
+#define LOGE(level, ...) if (level <= LOG_LEVEL) {__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__);}
+
+typedef void (*DecoderHandler) (short*, int);
+
+// Non-standard 'stdint' implementation
+#pragma clang diagnostic push
+#pragma ide diagnostic ignored "OCDFAInspection"
+extern "C"{
+ #ifdef __cplusplus
+ #define __STDC_CONSTANT_MACROS
+ #ifdef _STDINT_H
+ #undef _STDINT_H
+ #endif
+ # include
+ #endif
+}
+#ifndef INT64_C
+#define INT64_C(c) (c ## LL)
+#define UINT64_C(c) (c ## ULL)
+#endif
+
+#include <../interfaces/ffwrap.h>
+#include
+
+// FFmpeg implementation headers (using LGPLv3.0 model)
+extern "C" {
+ #define __STDC_CONSTANT_MACROS // workaround for compiler
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+}
+
+
+class VideoDecoder {
+ public:
+ VideoDecoder(AVFormatContext *pFormatCtx,
+ AVCodecContext *pCodecCtx,
+ AVStream* pStream,
+ int pStreamIndex,
+ IFFmpegWrapper *pInterface);
+ bool prepare();
+ bool decode(void *ptr);
+ int gBufferSize, gStreamIndex;
+ short* gBuffer;
+ bool gRunning;
+ AVFormatContext *gFormatCtx;
+ AVCodecContext *gCodecCtx;
+ AVStream *gStream;
+ IFFmpegWrapper *gInterface;
+ AVFrame *gFrame;
+ bool start();
+ bool stop();
+ void* decodeInThread();
+ short* convertYuv2Rgb(AVPixelFormat pxf, AVFrame* frame, int length);
+ double getPacketTime(AVPacket avPkt);
+ private:
+ PacketQueue* gPktQueue;
+};
+
+#endif //MOBILE_ANDROID_LEGACY_VIDEODEC_H
diff --git a/ndk-modules/ovkmplayer/interfaces/ffwrap.h b/ndk-modules/ovkmplayer/interfaces/ffwrap.h
new file mode 100644
index 000000000..06e442455
--- /dev/null
+++ b/ndk-modules/ovkmplayer/interfaces/ffwrap.h
@@ -0,0 +1,27 @@
+#ifndef MOBILE_ANDROID_LEGACY_INTERFACES_FFWRAP_H
+#define MOBILE_ANDROID_LEGACY_INTERFACES_FFWRAP_H
+
+#include
+
+#include
+
+class IFFmpegWrapper {
+ public:
+ IFFmpegWrapper() {};
+ virtual ~IFFmpegWrapper(){};
+ virtual void onError(int cmdId,
+ int errorCode) = 0;
+ virtual void onResult(int cmdId,
+ int resultCode) = 0;
+ virtual void onStreamDecoding(uint8_t *buffer,
+ int bufferLen,
+ int streamIndex) = 0;
+ virtual void onChangePlaybackState(int playbackState) = 0;
+ virtual void onChangeWrapperState(int wrapperState) = 0;
+ JNIEnv *env;
+ jobject instance;
+ private:
+ bool gDebugMode;
+};
+
+#endif
diff --git a/ndk-modules/ovkmplayer/jni/Android.mk b/ndk-modules/ovkmplayer/jni/Android.mk
new file mode 100644
index 000000000..e3344757e
--- /dev/null
+++ b/ndk-modules/ovkmplayer/jni/Android.mk
@@ -0,0 +1,54 @@
+# OPENVK LEGACY LICENSE NOTIFICATION
+#
+# This program is free software: you can redistribute it and/or modify it under the terms of
+# the GNU Affero General Public License as published by the Free Software Foundation, either
+# version 3 of the License, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License along with this
+# program. If not, see https://www.gnu.org/licenses/.
+#
+# Source code: https://github.com/openvk/mobile-android-legacy
+#
+# NOTICE: FFmpeg and FFmpeg custom builder for Android licensed under LGPLv3.0 or later version.
+
+LOCAL_PATH := $(call my-dir)
+PROJECT_PATH := $(call my-dir)/../../..
+ifeq ($(TARGET_ARCH_ABI),arm64-v8a)
+ FFMPEG_VERSION = 3.1.4
+else
+ FFMPEG_VERSION = 0.8.12
+endif
+FFMPEG_PATH = $(call my-dir)/builder/ffmpeg-$(FFMPEG_VERSION)
+#declare the prebuilt library
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := ffmpeg-prebuilt
+LOCAL_SRC_FILES := $(PROJECT_PATH)/app/src/main/jniLibs/$(TARGET_ARCH_ABI)/libffmpeg.so
+LOCAL_EXPORT_C_INCLUDES := $(PROJECT_PATH)/ndk-modules/ovkmplayer/builder/ffmpeg-$(FFMPEG_VERSION)/android/$(TARGET_ARCH_ABI)/include
+LOCAL_EXPORT_LDLIBS := $(PROJECT_PATH)/app/src/main/jniLibs/$(TARGET_ARCH_ABI)/libffmpeg.so
+LOCAL_PRELINK_MODULE := true
+LOCAL_LDFLAGS += -ljnigraphics
+
+include $(PREBUILT_SHARED_LIBRARY)
+
+#the andzop library
+include $(CLEAR_VARS)
+LOCAL_ALLOW_UNDEFINED_SYMBOLS=false
+LOCAL_MODULE := ovkmplayer
+ifeq ($(TARGET_ARCH_ABI),arm64-v8a)
+ LOCAL_SRC_FILES := ovkmplayer.cpp
+else
+ LOCAL_SRC_FILES := ovkmplayer-legacy.cpp
+endif
+LOCAL_C_INCLUDES := $(PROJECT_PATH)/ndk-modules/ovkmplayer/builder/ffmpeg-$(FFMPEG_VERSION)/android/$(TARGET_ARCH_ABI)/include
+LOCAL_C_INCLUDES += $(PROJECT_PATH)/ndk-modules/ovkmplayer/builder/ffmpeg-$(FFMPEG_VERSION)
+LOCAL_CPP_FEATURES := exceptions
+LOCAL_SHARED_LIBRARY := ffmpeg-prebuilt
+LOCAL_LDLIBS := -llog -lz -lm $(PROJECT_PATH)/app/src/main/jniLibs/$(TARGET_ARCH_ABI)/libffmpeg.so
+
+include $(BUILD_SHARED_LIBRARY)
+
diff --git a/ndk-modules/ovkmplayer/jni/Application.mk b/ndk-modules/ovkmplayer/jni/Application.mk
new file mode 100644
index 000000000..37403d3d5
--- /dev/null
+++ b/ndk-modules/ovkmplayer/jni/Application.mk
@@ -0,0 +1,18 @@
+# OPENVK LEGACY LICENSE NOTIFICATION
+#
+# This program is free software: you can redistribute it and/or modify it under the terms of
+# the GNU Affero General Public License as published by the Free Software Foundation, either
+# version 3 of the License, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License along with this
+# program. If not, see https://www.gnu.org/licenses/.
+#
+# Source code: https://github.com/openvk/mobile-android-legacy
+#
+# NOTICE: FFmpeg and FFmpeg custom builder for Android licensed under LGPLv3.0 or later version.
+
+APP_ABI := armeabi armeabi-v7a arm64-v8a x86
+APP_PLATFORM := android-5
diff --git a/ndk-modules/ovkmplayer/jni/ovkmplayer-legacy.cpp b/ndk-modules/ovkmplayer/jni/ovkmplayer-legacy.cpp
new file mode 100644
index 000000000..53d4cdbec
--- /dev/null
+++ b/ndk-modules/ovkmplayer/jni/ovkmplayer-legacy.cpp
@@ -0,0 +1,676 @@
+/**
+ * OPENVK LEGACY LICENSE NOTIFICATION
+ *
+ * This file is part of OpenVK Legacy.
+ *
+ * OpenVK Legacy is free software: you can redistribute it and/or modify it under the terms of
+ * the GNU Affero General Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License along with this
+ * program. If not, see https://www.gnu.org/licenses/.
+ *
+ * Source code: https://github.com/openvk/mobile-android-legacy
+ */
+
+// Java/C++ standard implementations headers
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+// Non-standard 'stdint' implementation
+#pragma clang diagnostic push
+#pragma ide diagnostic ignored "OCDFAInspection"
+extern "C"{
+ #ifdef __cplusplus
+ #define __STDC_CONSTANT_MACROS
+ #ifdef _STDINT_H
+ #undef _STDINT_H
+ #endif
+ # include
+ #endif
+}
+#ifndef INT64_C
+#define INT64_C(c) (c ## LL)
+#define UINT64_C(c) (c ## ULL)
+#endif
+
+// Android implementations headers
+#include
+
+// FFmpeg implementation headers (using LGPLv3.0 model)
+extern "C" {
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+}
+
+/*for Android logs*/
+#define LOG_TAG "OVK-MPLAY-LIB"
+#define LOG_LEVEL 10
+#define LOGD(level, ...) if (level <= LOG_LEVEL) {__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__);}
+#define LOGI(level, ...) if (level <= LOG_LEVEL) {__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__);}
+#define LOGE(level, ...) if (level <= LOG_LEVEL) {__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__);}
+
+char version[7] = "0.0.1";
+char *gFileName; //the file name of the video
+int gErrorCode;
+
+AVFormatContext *gFormatCtx;
+AVFormatContext *gTempFormatCtx;
+int gVideoStreamIndex; // video stream index
+int gAudioStreamIndex; // audio stream index
+
+AVCodecContext *gVideoCodecCtx;
+AVCodecContext *gAudioCodecCtx;
+
+jobject generateTrackInfo(JNIEnv* env, AVStream* pStream, AVCodec *pCodec, AVCodecContext *pCodecCtx, int type);
+
+bool debug_mode;
+
+AVDictionary *avFormatOptions = NULL;
+AVDictionary *avCodecOptions = NULL;
+
+jint g_playbackState;
+jint FFMPEG_PLAYBACK_STOPPED = 0;
+jint FFMPEG_PLAYBACK_PLAYING = 1;
+jint FFMPEG_PLAYBACK_PAUSED = 2;
+
+int gFrameCount;
+
+extern "C" {
+JNIEXPORT void JNICALL
+ Java_uk_openvk_android_legacy_utils_media_OvkMediaPlayer_initFFmpeg(JNIEnv *env, jobject instance) {
+ if(debug_mode) {
+ LOGD(10, "[DEBUG] Initializing FFmpeg...");
+ }
+ av_register_all();
+ avcodec_register_all();
+
+ }
+
+ JNIEXPORT jstring JNICALL
+ Java_uk_openvk_android_legacy_utils_media_OvkMediaPlayer_showLogo(JNIEnv *env, jobject instance) {
+ char logo[256] = "Logo";
+ sprintf(logo, "OpenVK Media Player ver. %s for Android [Legacy Mode]"
+ "\r\nOpenVK Media Player for Android is part of OpenVK Legacy Android app "
+ "licensed under AGPLv3 or later version."
+ "\r\nUsing FFmpeg licensed under LGPLv3 or later version.", version);
+ return env->NewStringUTF(logo);
+ }
+
+ JNIEXPORT void JNICALL
+ Java_uk_openvk_android_legacy_utils_media_OvkMediaPlayer_setDebugMode(
+ JNIEnv *env, jobject instance, jboolean value
+ ) {
+ if(value == JNI_TRUE) {
+ debug_mode = true;
+ LOGD(10, "[DEBUG] Enabled Debug Mode");
+ } else {
+ LOGD(10, "[DEBUG] Disabled Debug Mode");
+ }
+ }
+
+ JNIEXPORT jint JNICALL
+ Java_uk_openvk_android_legacy_utils_media_OvkMediaPlayer_openMediaFile(
+ JNIEnv *env, jobject instance,
+ jstring filename_) {
+ const char *filename = env->GetStringUTFChars(filename_, 0);
+ if(filename_ == NULL) {
+ LOGE(1, "[ERROR] Invalid filename");
+ return -10;
+ }
+ gFileName = (char *) filename;
+ if(debug_mode) {
+ LOGD(10, "[DEBUG] Opening file %s...", filename);
+ }
+
+ gFormatCtx = avformat_alloc_context();
+
+ if ((gErrorCode = avformat_open_input(&gFormatCtx, filename, NULL, 0)) != 0) {
+ char error_string[192];
+ if(gErrorCode == -2) {
+ sprintf(error_string, "File not found");
+ } else {
+ if (av_strerror(gErrorCode, error_string, 192) < 0) {
+ strerror_r(-gErrorCode, error_string, 192);
+ }
+ }
+ if(debug_mode) {
+ LOGE(1, "[ERROR] Can't open file: %d (%s)", gErrorCode, error_string);
+ }
+ return gErrorCode; //open file failed
+ }
+ if(debug_mode) {
+ LOGD(10, "[DEBUG] Searching A/V streams...", filename);
+ }
+ /*retrieve stream information*/
+ if ((gErrorCode = av_find_stream_info(gFormatCtx)) < 0) {
+ char error_string[192];
+ av_strerror(gErrorCode, error_string, 192);
+ if(debug_mode) {
+ LOGE(1, "[ERROR] Can't find stream information: %s (%d)", error_string, gErrorCode);
+ }
+ return gErrorCode;
+ }
+ }
+
+ AVFormatContext* openTempFile(char* filename) {
+ if(filename == NULL) {
+ LOGE(1, "[ERROR] Invalid filename");
+ return NULL;
+ }
+ if(debug_mode) {
+ LOGD(10, "[DEBUG] Opening temporary file %s...", filename);
+ }
+ if ((gErrorCode = av_open_input_file(&gTempFormatCtx, filename, NULL, 0, NULL)) != 0) {
+ char error_string[192];
+ if(gErrorCode == -2) {
+ sprintf(error_string, "File not found");
+ } else {
+ if (av_strerror(gErrorCode, error_string, 192) < 0) {
+ strerror_r(-gErrorCode, error_string, 192);
+ }
+ }
+ if(debug_mode) {
+ LOGE(1, "[ERROR] Can't open file: %d (%s)", gErrorCode, error_string);
+ }
+ return NULL; //open file failed
+ }
+ if(debug_mode) {
+ LOGD(10, "[DEBUG] Searching A/V streams...");
+ }
+ /*retrieve stream information*/
+ if ((gErrorCode = av_find_stream_info(gTempFormatCtx)) < 0) {
+ char error_string[192];
+ av_strerror(gErrorCode, error_string, 192);
+ if(debug_mode) {
+ LOGE(1, "[ERROR] Can't find stream information: %s (%d)", error_string, gErrorCode);
+ }
+ return NULL;
+ }
+ return gTempFormatCtx;
+ }
+
+ JNIEXPORT jint JNICALL
+ Java_uk_openvk_android_legacy_utils_media_OvkMediaPlayer_renderFrames
+ (JNIEnv *env, jobject instance, jobject buffer, jlong gFrameNumber) {
+ uint8_t* pFrameBuffer = (uint8_t *) (env)->GetDirectBufferAddress(buffer);
+ if(g_playbackState == FFMPEG_PLAYBACK_PLAYING) {
+ int err, i, got_frame, frame_size;
+ AVDictionaryEntry *e;
+ AVCodecContext *pCodecCtx = NULL;
+ AVCodec *pCodec = NULL;
+ AVFrame *pFrame = NULL;
+ AVFrame *pFrameRGB = NULL;
+ AVPacket packet;
+ int endOfVideo;
+ uint8_t *output_buf;
+
+ AVStream *pVideoStream = gFormatCtx->streams[gVideoStreamIndex];
+ pCodecCtx = gFormatCtx->streams[gVideoStreamIndex]->codec;
+ pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
+
+ if(pCodec == NULL) {
+ if(debug_mode) {
+ LOGE(1, "[ERROR] Video stream found, but '%s' decoder is unavailable.",
+ gVideoCodecCtx->codec_name);
+ }
+ g_playbackState = FFMPEG_PLAYBACK_STOPPED;
+ gErrorCode = -2;
+ return gErrorCode;
+ }
+
+ e = NULL;
+ while ((e = av_dict_get(avCodecOptions, "", e, AV_DICT_IGNORE_SUFFIX))) {
+ if(debug_mode) {
+ LOGE(10, "avcodec_open2: option \"%s\" not recognized", e->key);
+ gErrorCode = -2;
+ return gErrorCode;
+ }
+ }
+
+ pFrame = avcodec_alloc_frame();
+ pFrameRGB = avcodec_alloc_frame();
+ frame_size = avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,
+ pCodecCtx->height);
+ pFrameBuffer = (uint8_t*) av_malloc(frame_size * sizeof(uint8_t));
+ if(pFrame == NULL || pFrameRGB == NULL) {
+ if(debug_mode) {
+ LOGE(10, "[ERROR] Cannot allocate video frames");
+ }
+ g_playbackState = FFMPEG_PLAYBACK_STOPPED;
+ gErrorCode = -3;
+ return gErrorCode;
+ } else if (packet.stream_index == gVideoStreamIndex) {
+ int ret = av_read_frame(gFormatCtx, &packet);
+ if(ret >= 0) {
+ avpicture_fill((AVPicture *) pFrameRGB, pFrameBuffer, PIX_FMT_RGB24,
+ pCodecCtx->width, pCodecCtx->height);
+ avcodec_decode_video2(pCodecCtx, pFrame, &got_frame, &packet);
+ if (got_frame) {
+ pFrameBuffer = (uint8_t *) pFrameRGB->data;
+ }
+ av_free_packet(&packet);
+ return 1;
+ } else if(ret == AVERROR_EOF){
+ g_playbackState = FFMPEG_PLAYBACK_STOPPED;
+ return 0;
+ }
+ }
+ }
+ }
+
+ JNIEXPORT jobject JNICALL
+ Java_uk_openvk_android_legacy_utils_media_OvkMediaPlayer_setPlaybackState
+ (JNIEnv *env, jobject instance, jint state) {
+ g_playbackState = state;
+ if(state == FFMPEG_PLAYBACK_PLAYING) {
+ if(debug_mode) {
+ LOGD(1, "[DEBUG] Setting playback state to \"Playing\"...");
+ }
+ } else if(state == FFMPEG_PLAYBACK_PAUSED){
+ if(debug_mode) {
+ LOGD(1, "[DEBUG] Setting playback state to \"Paused\"...");
+ }
+ } else if(state == FFMPEG_PLAYBACK_STOPPED) {
+ if(debug_mode) {
+ LOGD(1, "[DEBUG] Setting playback state to \"Stopped\"...");
+ }
+ }
+ }
+
+ JNIEXPORT jint JNICALL
+ Java_uk_openvk_android_legacy_utils_media_OvkMediaPlayer_getPlaybackState
+ (JNIEnv *env, jobject instance) {
+ return g_playbackState;
+ }
+
+ JNIEXPORT jobject JNICALL
+ Java_uk_openvk_android_legacy_utils_media_OvkMediaPlayer_getTrackInfo(
+ JNIEnv *env, jobject instance,
+ jstring filename_,
+ jint type) {
+ int videoStreamIndex = -1; // video stream index
+ int audioStreamIndex = -1; // audio stream index
+
+ AVCodecContext *videoCodecCtx = NULL;
+ AVCodecContext *audioCodecCtx = NULL;
+ const char *filename;
+ if(gTempFormatCtx == NULL) {
+ if (filename_ == NULL) {
+ if (Java_uk_openvk_android_legacy_utils_media_OvkMediaPlayer_openMediaFile
+ (env, instance, reinterpret_cast(gFileName)) < 0) {
+ return NULL;
+ } else {
+ gTempFormatCtx = gFormatCtx;
+ }
+ } else {
+ filename = env->GetStringUTFChars(filename_, 0);
+ gTempFormatCtx = openTempFile(const_cast(filename));
+ if (gTempFormatCtx == NULL) {
+ return NULL;
+ }
+ }
+ }
+
+ if (type == 0) {
+ AVCodec *lVideoCodec;
+ /*some global variables initialization*/
+ if(debug_mode) {
+ LOGD(10, "[DEBUG] Getting video track info...");
+ }
+
+ /*find the video stream and its decoder*/
+ gVideoStreamIndex = av_find_best_stream(gTempFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1,
+ &lVideoCodec,
+ 0);
+
+ if (videoStreamIndex == AVERROR_STREAM_NOT_FOUND) {
+ if(debug_mode) {
+ LOGE(1, "[ERROR] Cannot find a video stream");
+ }
+ return NULL;
+ }
+ if (videoStreamIndex == AVERROR_DECODER_NOT_FOUND) {
+ if(debug_mode) {
+ LOGE(1, "[ERROR] Video stream found, but '%s' decoder is unavailable.",
+ lVideoCodec->name);
+ }
+ return NULL;
+ }
+
+ if(debug_mode) {
+ LOGD(10, "[DEBUG] Total streams: %d | Video stream #%d detected. Opening...",
+ gTempFormatCtx->nb_streams, videoStreamIndex + 1);
+ }
+
+ /*open the codec*/
+ gVideoCodecCtx = gTempFormatCtx->streams[videoStreamIndex]->codec;
+ LOGI(10, "[INFO] Video codec: %s | Frame size: %dx%d", videoCodecCtx->codec_name,
+ videoCodecCtx->height, videoCodecCtx->width);
+ #ifdef SELECTIVE_DECODING
+ gVideoCodecCtx->allow_selective_decoding = 1;
+ #endif
+ if (avcodec_open(videoCodecCtx, lVideoCodec) < 0) {
+ if(debug_mode) {
+ LOGE(1, "[ERROR] Can't open the video codec!");
+ }
+ return NULL;
+ }
+ return generateTrackInfo(
+ env, gTempFormatCtx->streams[videoStreamIndex],
+ lVideoCodec, videoCodecCtx, AVMEDIA_TYPE_VIDEO
+ );
+ } else {
+ AVCodec *lAudioCodec;
+
+ if(debug_mode) {
+ LOGD(10, "[DEBUG] Getting audio track info...");
+ }
+
+ /*find the audio stream and its decoder*/
+ gAudioStreamIndex = av_find_best_stream(gTempFormatCtx, AVMEDIA_TYPE_AUDIO, -1, -1,
+ &lAudioCodec,
+ 0);
+
+ if (gAudioStreamIndex == AVERROR_STREAM_NOT_FOUND) {
+ if(debug_mode) {
+ LOGE(1, "[ERROR] Cannot find a audio stream");
+ }
+ return NULL;
+ }
+ if (gAudioStreamIndex == AVERROR_DECODER_NOT_FOUND) {
+ if(debug_mode) {
+ LOGE(1, "[ERROR] Audio stream found, but '%s' decoder is unavailable.",
+ lAudioCodec->name);
+ }
+ return NULL;
+ }
+
+ /*open the codec*/
+ if(debug_mode) {
+ LOGD(10, "[DEBUG] Total streams: %d | Audio stream #%d detected. Opening...",
+ gTempFormatCtx->nb_streams, audioStreamIndex + 1);
+ }
+ LOGI(10, "[INFO] Duration: %d", gTempFormatCtx->streams[audioStreamIndex]->duration);
+ audioCodecCtx = gFormatCtx->streams[audioStreamIndex]->codec;
+ LOGI(10, "[INFO] Audio codec: %s | Sample rate: %d Hz", audioCodecCtx->codec_name,
+ audioCodecCtx->sample_rate);
+ #ifdef SELECTIVE_DECODING
+ gAudioCodecCtx->allow_selective_decoding = 1;
+ #endif
+ if (avcodec_open(audioCodecCtx, lAudioCodec) < 0) {
+ if(debug_mode) {
+ LOGE(1, "[ERROR] Can't open the audio codec!");
+ }
+ return NULL;
+ }
+ return generateTrackInfo(
+ env, gTempFormatCtx->streams[audioStreamIndex],
+ lAudioCodec, audioCodecCtx, AVMEDIA_TYPE_AUDIO
+ );
+ }
+ if(filename_ != NULL)
+ env->ReleaseStringUTFChars(filename_, filename);
+ };
+
+ JNIEXPORT jobject JNICALL
+ Java_uk_openvk_android_legacy_utils_media_OvkMediaPlayer_getTrackInfo2(
+ JNIEnv *env, jobject instance,
+ jint type) {
+
+ int videoStreamIndex = -1; // video stream index
+ int audioStreamIndex = -1; // audio stream index
+
+ AVCodecContext *videoCodecCtx = NULL;
+ AVCodecContext *audioCodecCtx = NULL;
+ const char *filename;
+ if(gFormatCtx == NULL) {
+ if (gTempFormatCtx == NULL) {
+ if (openTempFile(gFileName) == NULL) {
+ return NULL;
+ }
+ }
+ } else {
+ gTempFormatCtx = gFormatCtx;
+ }
+ try {
+ if (type == 0) {
+ AVCodec *lVideoCodec;
+ /*some global variables initialization*/
+ if (debug_mode) {
+ LOGD(10, "[DEBUG] Getting video track info...");
+ }
+
+ /*find the video stream and its decoder*/
+ gVideoStreamIndex = av_find_best_stream(gTempFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1,
+ &lVideoCodec,
+ 0);
+
+ if (videoStreamIndex == AVERROR_STREAM_NOT_FOUND) {
+ if (debug_mode) {
+ LOGE(1, "[ERROR] Cannot find a video stream");
+ }
+ return NULL;
+ }
+ if (videoStreamIndex == AVERROR_DECODER_NOT_FOUND) {
+ if (debug_mode) {
+ LOGE(1, "[ERROR] Video stream found, but '%s' decoder is unavailable.",
+ lVideoCodec->name);
+ }
+ return NULL;
+ }
+
+ if (debug_mode) {
+ LOGD(10, "[DEBUG] Total streams: %d | Video stream #%d detected. Opening...",
+ gTempFormatCtx->nb_streams, videoStreamIndex + 1);
+ }
+
+ /*open the codec*/
+ gVideoCodecCtx = gTempFormatCtx->streams[videoStreamIndex]->codec;
+ LOGI(10, "[INFO] Video codec: %s | Frame size: %dx%d", videoCodecCtx->codec_name,
+ videoCodecCtx->height, videoCodecCtx->width);
+ #ifdef SELECTIVE_DECODING
+ gVideoCodecCtx->allow_selective_decoding = 1;
+ #endif
+ if (avcodec_open(videoCodecCtx, lVideoCodec) < 0) {
+ if (debug_mode) {
+ LOGE(1, "[ERROR] Can't open the video codec!");
+ }
+ return NULL;
+ }
+ return generateTrackInfo(
+ env, gTempFormatCtx->streams[videoStreamIndex],
+ lVideoCodec, videoCodecCtx, AVMEDIA_TYPE_VIDEO
+ );
+ } else {
+ AVCodec *lAudioCodec;
+
+ if (debug_mode) {
+ LOGD(10, "[DEBUG] Getting audio track info...");
+ }
+
+ /*find the audio stream and its decoder*/
+ gAudioStreamIndex = av_find_best_stream(gTempFormatCtx, AVMEDIA_TYPE_AUDIO, -1, -1,
+ &lAudioCodec,
+ 0);
+
+ if (gAudioStreamIndex == AVERROR_STREAM_NOT_FOUND) {
+ if (debug_mode) {
+ LOGE(1, "[ERROR] Cannot find a audio stream");
+ }
+ return NULL;
+ }
+ if (gAudioStreamIndex == AVERROR_DECODER_NOT_FOUND) {
+ if (debug_mode) {
+ LOGE(1, "[ERROR] Audio stream found, but '%s' decoder is unavailable.",
+ lAudioCodec->name);
+ }
+ return NULL;
+ }
+
+ /*open the codec*/
+ if (debug_mode) {
+ LOGD(10, "[DEBUG] Total streams: %d | Audio stream #%d detected. Opening...",
+ gTempFormatCtx->nb_streams, audioStreamIndex + 1);
+ }
+ LOGI(10, "[INFO] Duration: %d",
+ gTempFormatCtx->streams[audioStreamIndex]->duration);
+ lAudioCodec = avcodec_find_decoder(
+ gTempFormatCtx->streams[audioStreamIndex]->codec->codec_id);
+
+ audioCodecCtx = lAudioCodec->;
+ LOGI(10, "[INFO] Audio codec: %s | Sample rate: %d Hz", audioCodecCtx->codec_name,
+ audioCodecCtx->sample_rate);
+ #ifdef SELECTIVE_DECODING
+ gAudioCodecCtx->allow_selective_decoding = 1;
+ #endif
+ if (avcodec_open(audioCodecCtx, lAudioCodec) < 0) {
+ if (debug_mode) {
+ LOGE(1, "[ERROR] Can't open the audio codec!");
+ }
+ return NULL;
+ }
+ return generateTrackInfo(
+ env, gTempFormatCtx->streams[audioStreamIndex],
+ lAudioCodec, audioCodecCtx, AVMEDIA_TYPE_AUDIO
+ );
+ }
+ } catch (const char* error_message) {
+ return NULL;
+ }
+ };
+
+ JNIEXPORT jint JNICALL
+ Java_uk_openvk_android_legacy_utils_media_OvkMediaPlayer_getLastErrorCode(
+ JNIEnv *env, jobject instance
+ ) {
+ return gErrorCode;
+ }
+
+ JNIEXPORT jlong JNICALL
+ Java_uk_openvk_android_legacy_utils_media_OvkMediaPlayer_getFrameCount(
+ JNIEnv *env, jobject instance
+ ) {
+ return gFrameCount;
+ }
+
+
+ JNIEXPORT jstring JNICALL
+ Java_uk_openvk_android_legacy_utils_media_OvkMediaPlayer_getLastErrorString(
+ JNIEnv *env, jobject instance
+ ) {
+ char error_str[192];
+ av_strerror(gErrorCode, error_str, 192);
+ return env->NewStringUTF(error_str);
+ }
+};
+
+jobject generateTrackInfo(
+ JNIEnv* env, AVStream* pStream, AVCodec *pCodec, AVCodecContext *pCodecCtx, int type
+) {
+ // JNI field types (bad stuff, don't you agree?)
+ // [JNI] => [java]
+ // "[?" => ?[] == int[], bool[], long[] and etc.
+ // "I" => int
+ // "J" => long (aka. int64)
+ // "Z" => boolean
+ // "D" => double
+ // "F" => float
+
+ jclass track_class;
+ try {
+ if (type == AVMEDIA_TYPE_VIDEO) {
+ // Load OvkVideoTrack class
+ track_class = env->FindClass(
+ "uk/openvk/android/legacy/utils/media/OvkVideoTrack"
+ );
+ // Load OvkVideoTrack class method
+ jmethodID video_track_init = env->GetMethodID(
+ track_class, "", "()V"
+ );
+ jfieldID codec_name_field = env->GetFieldID(
+ track_class, "codec_name", "Ljava/lang/String;"
+ );
+ jfieldID frame_size_field = env->GetFieldID(track_class, "frame_size", "[I");
+ jfieldID bitrate_field = env->GetFieldID(
+ track_class, "bitrate", "I"
+ );
+ jfieldID frame_rate_field = env->GetFieldID(
+ track_class, "frame_rate", "F"
+ );
+
+ jobject track = env->NewObject(track_class, video_track_init);
+
+ // Load OvkVideoTrack values form fields (class variables)
+ env->SetObjectField(track, codec_name_field, env->NewStringUTF(pCodec->name));
+ jintArray array = (jintArray) env->GetObjectField(track, frame_size_field);
+ jint *frame_size = env->GetIntArrayElements(array, 0);
+ frame_size[0] = pCodecCtx->width;
+ frame_size[1] = pCodecCtx->height;
+ env->ReleaseIntArrayElements(array, frame_size, 0);
+ env->SetIntField(track, bitrate_field, pCodecCtx->bit_rate);
+ env->SetFloatField(track, frame_rate_field, pStream->avg_frame_rate.num);
+ return track;
+ } else {
+ // Load OvkAudioTrack class
+ track_class = env->FindClass(
+ "uk/openvk/android/legacy/utils/media/OvkAudioTrack"
+ );
+ // Load OvkVideoTrack class methods
+ jmethodID audio_track_init = env->GetMethodID(
+ track_class, "", "()V"
+ );
+
+ jobject track = env->NewObject(track_class, audio_track_init);
+
+ jfieldID codec_name_field = env->GetFieldID(
+ track_class, "codec_name", "Ljava/lang/String;"
+ );
+ jfieldID sample_rate_field = env->GetFieldID(
+ track_class, "sample_rate", "J"
+ );
+ jfieldID bitrate_field = env->GetFieldID(
+ track_class, "bitrate", "J"
+ );
+ jfieldID channels_field = env->GetFieldID(
+ track_class, "channels", "I"
+ );
+
+ // Load OvkAudioTrack values form fields (class variables)
+ env->SetObjectField(track, codec_name_field, env->NewStringUTF(pCodec->name));
+ env->SetLongField(track, sample_rate_field, pCodecCtx->sample_rate);
+ env->SetLongField(track, bitrate_field, pCodecCtx->bit_rate);
+ env->SetIntField(track, channels_field, pCodecCtx->channels);
+ return track;
+ }
+ } catch (...) {
+ if(debug_mode) {
+ LOGE(1, "[ERROR] Track not found");
+ }
+ return NULL;
+ }
+ return NULL;
+}
+#pragma clang diagnostic pop
diff --git a/ndk-modules/ovkmplayer/jni/ovkmplayer.cpp b/ndk-modules/ovkmplayer/jni/ovkmplayer.cpp
new file mode 100644
index 000000000..6af3a40b4
--- /dev/null
+++ b/ndk-modules/ovkmplayer/jni/ovkmplayer.cpp
@@ -0,0 +1,457 @@
+/**
+ * OPENVK LEGACY LICENSE NOTIFICATION
+ *
+ * This file is part of OpenVK Legacy.
+ *
+ * OpenVK Legacy is free software: you can redistribute it and/or modify it under the terms of
+ * the GNU Affero General Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License along with this
+ * program. If not, see https://www.gnu.org/licenses/.
+ *
+ * Source code: https://github.com/openvk/mobile-android-legacy
+ */
+
+// Java/C++ standard implementations headers
+#include
+#include
+
+// Android implementations headers
+#include
+
+// FFmpeg implementation headers (using LGPLv3.0 model)
+extern "C" {
+ #include
+ #include
+ #include
+ #include
+}
+
+/*for Android logs*/
+#define LOG_TAG "OVK-MPLAY-LIB"
+#define LOG_LEVEL 10
+#define LOGD(level, ...) if (level <= LOG_LEVEL) {__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__);}
+#define LOGI(level, ...) if (level <= LOG_LEVEL) {__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__);}
+#define LOGE(level, ...) if (level <= LOG_LEVEL) {__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__);}
+
+char version[7] = "0.0.1";
+char *gFileName; //file name of the video
+int gErrorCode;
+
+AVFormatContext *gFormatCtx;
+int gVideoStreamIndex; // video stream index
+int gAudioStreamIndex; // audio stream index
+
+AVCodecContext *gVideoCodecCtx;
+AVCodecContext *gAudioCodecCtx;
+
+AVDictionary *avFormatOptions = NULL;
+AVDictionary *avCodecOptions = NULL;
+
+bool debug_mode;
+
+jint g_playbackState;
+jint FFMPEG_PLAYBACK_STOPPED = 0;
+jint FFMPEG_PLAYBACK_PLAYING = 1;
+jint FFMPEG_PLAYBACK_PAUSED = 2;
+
+jlong gFrameCount;
+
+jobject generateTrackInfo(JNIEnv* env, jobject instance,
+ AVStream* pStream, AVCodec *pCodec, AVCodecContext *pCodecCtx, int type);
+
+extern "C" {
+ JNIEXPORT void JNICALL
+ Java_uk_openvk_android_legacy_utils_media_OvkMediaPlayer_initFFmpeg(JNIEnv *env, jobject instance) {
+ if(debug_mode) {
+ LOGD(10, "[DEBUG] Initializing FFmpeg...");
+ }
+ av_register_all();
+ avcodec_register_all();
+ gFormatCtx = avformat_alloc_context();
+ }
+
+ JNIEXPORT jstring JNICALL
+ Java_uk_openvk_android_legacy_utils_media_OvkMediaPlayer_showLogo(JNIEnv *env, jobject instance) {
+ char logo[256] = "Logo";
+ sprintf(logo, "OpenVK Media Player ver. %s for Android"
+ "\r\nOpenVK Media Player for Android is part of OpenVK Legacy Android app "
+ "licensed under AGPLv3 or later version."
+ "\r\nUsing FFmpeg licensed under LGPLv3 or later version.", version);
+ return env->NewStringUTF(logo);
+ }
+
+ JNIEXPORT void JNICALL
+ Java_uk_openvk_android_legacy_utils_media_OvkMediaPlayer_setDebugMode(
+ JNIEnv *env, jobject instance, jboolean value
+ ) {
+ if(value == JNI_TRUE) {
+ debug_mode = true;
+ LOGD(10, "[DEBUG] Enabled Debug Mode");
+ } else {
+ LOGD(10, "[DEBUG] Disabled Debug Mode");
+ }
+ }
+
+ JNIEXPORT jint JNICALL
+ Java_uk_openvk_android_legacy_utils_media_OvkMediaPlayer_openMediaFile(
+ JNIEnv *env, jobject instance,
+ jstring filename_) {
+ if(filename_ == NULL) {
+ LOGE(1, "[ERROR] Invalid filename");
+ g_playbackState = 0;
+ return -10;
+ }
+ const char *filename = env->GetStringUTFChars(filename_, 0);
+ gFileName = (char *) filename;
+ if(debug_mode) {
+ LOGD(10, "[DEBUG] Opening file %s...", filename);
+ }
+ if ((gErrorCode = avformat_open_input(&gFormatCtx, filename, NULL, NULL)) != 0) {
+ if(debug_mode) {
+ char error_string[192];
+ if(av_strerror(gErrorCode, error_string, 192) < 0) {
+ strerror_r(-gErrorCode, error_string, 192);
+ }
+ LOGE(1, "[ERROR] Can't open file: %d (%s)", gErrorCode, error_string);
+ }
+ g_playbackState = 0;
+ return gErrorCode; //open file failed
+ }
+ /*retrieve stream information*/
+ if ((gErrorCode = avformat_find_stream_info(gFormatCtx, NULL)) < 0) {
+ char error_string[192];
+ av_strerror(gErrorCode, error_string, 192);
+ if(debug_mode) {
+ LOGE(1, "[ERROR] Can't find stream information: %s (%d)", error_string, gErrorCode);
+ }
+ g_playbackState = 0;
+ return gErrorCode;
+ }
+ }
+
+ JNIEXPORT jint JNICALL
+ Java_uk_openvk_android_legacy_utils_media_OvkMediaPlayer_renderFrames
+ (JNIEnv *env, jobject instance, jobject buffer, jlong gFrameNumber) {
+ uint8_t* pFrameBuffer = (uint8_t *) (env)->GetDirectBufferAddress(buffer);
+ if(g_playbackState == FFMPEG_PLAYBACK_PLAYING) {
+ int err, i, got_frame, frame_size;
+ AVDictionaryEntry *e;
+ AVCodecContext *pCodecCtx = NULL;
+ AVCodec *pCodec = NULL;
+ AVFrame *pFrame = NULL;
+ AVFrame *pFrameRGB = NULL;
+ AVPacket packet;
+ int endOfVideo;
+ uint8_t *output_buf;
+
+ AVStream *pVideoStream = gFormatCtx->streams[gVideoStreamIndex];
+ pCodecCtx = gFormatCtx->streams[gVideoStreamIndex]->codec;
+ pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
+
+ if(pCodec == NULL) {
+ if(debug_mode) {
+ LOGE(1, "[ERROR] Video stream found, but decoder is unavailable.");
+ }
+ g_playbackState = FFMPEG_PLAYBACK_STOPPED;
+ gErrorCode = -2;
+ return gErrorCode;
+ }
+
+ e = NULL;
+ while ((e = av_dict_get(avCodecOptions, "", e, AV_DICT_IGNORE_SUFFIX))) {
+ if(debug_mode) {
+ LOGE(10, "avcodec_open2: option \"%s\" not recognized", e->key);
+ gErrorCode = -2;
+ return gErrorCode;
+ }
+ }
+
+ pFrame = av_frame_alloc();
+ pFrameRGB = av_frame_alloc();
+ frame_size = avpicture_get_size(AV_PIX_FMT_RGB24, pCodecCtx->width,
+ pCodecCtx->height);
+ pFrameBuffer = (uint8_t*) av_malloc(frame_size * sizeof(uint8_t));
+ if(pFrame == NULL || pFrameRGB == NULL) {
+ if(debug_mode) {
+ LOGE(10, "[ERROR] Cannot allocate video frames");
+ }
+ g_playbackState = FFMPEG_PLAYBACK_STOPPED;
+ gErrorCode = -3;
+ return gErrorCode;
+ } else if (packet.stream_index == gVideoStreamIndex) {
+ int ret = av_read_frame(gFormatCtx, &packet);
+ if(ret >= 0) {
+ avpicture_fill((AVPicture *) pFrameRGB, pFrameBuffer, AV_PIX_FMT_RGB24,
+ pCodecCtx->width, pCodecCtx->height);
+ avcodec_decode_video2(pCodecCtx, pFrame, &got_frame, &packet);
+ if (got_frame) {
+ pFrameBuffer = (uint8_t *) pFrameRGB->data;
+ }
+ av_free_packet(&packet);
+ return 1;
+ } else if(ret == AVERROR_EOF){
+ g_playbackState = FFMPEG_PLAYBACK_STOPPED;
+ return 0;
+ }
+ }
+ }
+ }
+
+ JNIEXPORT jobject JNICALL
+ Java_uk_openvk_android_legacy_utils_media_OvkMediaPlayer_getTrackInfo(
+ JNIEnv *env, jobject instance,
+ jstring filename_,
+ jint type) {
+ const char *filename = env->GetStringUTFChars(filename_, 0);
+ if(gFileName == NULL) {
+ gFileName = (char *) filename;
+ if(Java_uk_openvk_android_legacy_utils_media_OvkMediaPlayer_openMediaFile
+ (env, instance, filename_) < 0) {
+ return NULL;
+ }
+ }
+ if (type == 0) {
+ AVCodec *lVideoCodec;
+ /*some global variables initialization*/
+ if(debug_mode) {
+ LOGI(10, "[DEBUG] Getting video track info...");
+ }
+
+ /*find the video stream and its decoder*/
+ gVideoStreamIndex = av_find_best_stream(gFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1,
+ &lVideoCodec,
+ 0);
+ if (gVideoStreamIndex == AVERROR_STREAM_NOT_FOUND) {
+ if(debug_mode) {
+ LOGE(1, "[ERROR] Cannot find a video stream");
+ }
+ g_playbackState = 0;
+ return NULL;
+ } else {
+ if(debug_mode) {
+ LOGI(10, "[INFO] Video codec: %s", lVideoCodec->name);
+ }
+ }
+ if (gVideoStreamIndex == AVERROR_DECODER_NOT_FOUND) {
+ if(debug_mode) {
+ LOGE(1, "[ERROR] Video stream found, but '%s' decoder is unavailable.",
+ lVideoCodec->name);
+ }
+ g_playbackState = 0;
+ return NULL;
+ }
+
+ if(debug_mode) {
+ LOGD(10, "[DEBUG] Total streams: %d | Video stream #%d detected. Opening...",
+ gFormatCtx->nb_streams, gVideoStreamIndex + 1);
+ }
+
+ /*open the codec*/
+ gVideoCodecCtx = gFormatCtx->streams[gVideoStreamIndex]->codec;
+ if(debug_mode) {
+ LOGI(10, "[INFO] Frame size: %dx%d", gVideoCodecCtx->height, gVideoCodecCtx->width);
+ }
+ #ifdef SELECTIVE_DECODING
+ gVideoCodecCtx->allow_selective_decoding = 1;
+ #endif
+ if (avcodec_open2(gVideoCodecCtx, lVideoCodec, NULL) < 0) {
+ if(debug_mode) {
+ LOGE(1, "[ERROR] Can't open the video codec!");
+ }
+ g_playbackState = 0;
+ return NULL;
+ }
+ return generateTrackInfo(
+ env, instance, gFormatCtx->streams[gVideoStreamIndex],
+ lVideoCodec, gVideoCodecCtx, AVMEDIA_TYPE_VIDEO
+ );
+ } else {
+ AVCodec *lAudioCodec;
+ if(debug_mode) {
+ LOGI(10, "[DEBUG] Getting audio track info...");
+ }
+
+ /*find the video stream and its decoder*/
+ gAudioStreamIndex = av_find_best_stream(gFormatCtx, AVMEDIA_TYPE_AUDIO, -1, -1,
+ &lAudioCodec,
+ 0);
+
+ if (gAudioStreamIndex == AVERROR_STREAM_NOT_FOUND) {
+ if(debug_mode) {
+ LOGE(1, "[ERROR] Cannot find a audio stream");
+ }
+ return NULL;
+ }
+
+ if (gAudioStreamIndex == AVERROR_DECODER_NOT_FOUND) {
+ if(debug_mode) {
+ LOGE(1, "[ERROR] Audio stream found, but '%s' decoder is unavailable.",
+ lAudioCodec->name);
+ }
+ return NULL;
+ }
+
+ if(debug_mode) {
+ LOGD(10, "[DEBUG] Total streams: %d | Audio stream #%d detected. Opening...",
+ gFormatCtx->nb_streams, gAudioStreamIndex + 1);
+ }
+
+ /*open the codec*/
+ gAudioCodecCtx = gFormatCtx->streams[gAudioStreamIndex]->codec;
+ #ifdef SELECTIVE_DECODING
+ gAudioCodecCtx->allow_selective_decoding = 1;
+ #endif
+ if (avcodec_open2(gAudioCodecCtx, lAudioCodec, NULL) < 0) {
+ if(debug_mode) {
+ LOGE(1, "[ERROR] Can't open the audio codec!");
+ }
+ return NULL;
+ }
+ return generateTrackInfo(
+ env, instance, gFormatCtx->streams[gAudioStreamIndex],
+ lAudioCodec, gAudioCodecCtx, AVMEDIA_TYPE_AUDIO
+ );
+ }
+
+ env->ReleaseStringUTFChars(filename_, filename);
+ };
+
+ JNIEXPORT jobject JNICALL
+ Java_uk_openvk_android_legacy_utils_media_OvkMediaPlayer_setPlaybackState
+ (JNIEnv *env, jobject instance, jint state) {
+ g_playbackState = state;
+ if(state == FFMPEG_PLAYBACK_PLAYING) {
+ if(debug_mode) {
+ LOGD(1, "[DEBUG] Setting playback state to \"Playing\"...");
+ }
+ } else if(state == FFMPEG_PLAYBACK_PAUSED){
+ if(debug_mode) {
+ LOGD(1, "[DEBUG] Setting playback state to \"Paused\"...");
+ }
+ } else if(state == FFMPEG_PLAYBACK_STOPPED) {
+ if(debug_mode) {
+ LOGD(1, "[DEBUG] Setting playback state to \"Stopped\"...");
+ }
+ }
+ }
+
+ JNIEXPORT jint JNICALL
+ Java_uk_openvk_android_legacy_utils_media_OvkMediaPlayer_getPlaybackState
+ (JNIEnv *env, jobject instance) {
+ return g_playbackState;
+ }
+
+ JNIEXPORT jlong JNICALL
+ Java_uk_openvk_android_legacy_utils_media_OvkMediaPlayer_getFrameCount(
+ JNIEnv *env, jobject instance
+ ) {
+ return gFrameCount;
+ }
+
+ JNIEXPORT jint JNICALL
+ Java_uk_openvk_android_legacy_utils_media_OvkMediaPlayer_getLastErrorCode(
+ JNIEnv *env, jobject instance
+ ) {
+ return gErrorCode;
+ }
+
+ JNIEXPORT jstring JNICALL
+ Java_uk_openvk_android_legacy_utils_media_OvkMediaPlayer_getLastErrorString(
+ JNIEnv *env, jobject instance
+ ) {
+ char error_str[192];
+ av_strerror(gErrorCode, error_str, 192);
+ return env->NewStringUTF(error_str);
+ }
+}
+
+jobject generateTrackInfo(
+ JNIEnv* env, jobject instance, AVStream* pStream, AVCodec *pCodec, AVCodecContext *pCodecCtx, int type
+) {
+ // JNI field types (bad stuff, don't you agree?)
+ // [JNI] => [java]
+ // "[?" => ?[] == int[], bool[], long[] and etc.
+ // "I" => int
+ // "J" => long (aka. int64)
+ // "Z" => boolean
+ // "D" => double
+ // "F" => float
+
+ jclass track_class;
+ jobject track;
+ try {
+ if (type == AVMEDIA_TYPE_VIDEO) {
+ // Load OvkVideoTrack class
+ track_class = env->FindClass(
+ "uk/openvk/android/legacy/utils/media/OvkVideoTrack"
+ );
+
+ // Load OvkVideoTrack class methods
+ jmethodID video_track_init = env->GetMethodID(
+ track_class, "", "()V"
+ );
+ jfieldID codec_name_field = env->GetFieldID(
+ track_class, "codec_name", "Ljava/lang/String;"
+ );
+ jfieldID frame_size_field = env->GetFieldID(track_class, "frame_size", "[I");
+ jfieldID bitrate_field = env->GetFieldID(
+ track_class, "bitrate", "Ljava/lang/Integer;"
+ );
+ jfieldID frame_rate_field = env->GetFieldID(
+ track_class, "frame_rate", "Ljava/lang/Float;"
+ );
+
+ track = env->NewObject(track_class, video_track_init);
+
+ // Load OvkVideoTrack values form fields (class variables)
+ env->SetObjectField(track, codec_name_field, env->NewStringUTF(pCodec->name));
+ jintArray array = (jintArray) env->GetObjectField(track, frame_size_field);
+ jint *frame_size = env->GetIntArrayElements(array, 0);
+ frame_size[0] = pCodecCtx->width;
+ frame_size[1] = pCodecCtx->height;
+ env->ReleaseIntArrayElements(array, frame_size, 0);
+ env->SetIntField(track, bitrate_field, pCodecCtx->bit_rate);
+ env->SetFloatField(track, frame_rate_field, pStream->avg_frame_rate.num);
+ } else {
+ // Load OvkAudioTrack class
+ track_class = env->FindClass(
+ "uk/openvk/android/legacy/utils/media/OvkAudioTrack"
+ );
+ // Load OvkVideoTrack class methods
+ jmethodID audio_track_init = env->GetMethodID(
+ track_class, "", "()V"
+ );
+ jfieldID codec_name_field = env->GetFieldID(
+ track_class, "codec_name", "Ljava/lang/String;"
+ );
+ jfieldID bitrate_field = env->GetFieldID(
+ track_class, "bitrate", "J"
+ );
+ jfieldID sample_rate_field = env->GetFieldID(
+ track_class, "sample_rate", "J"
+ );
+ jfieldID channels_field = env->GetFieldID(
+ track_class, "channels", "I"
+ );
+
+ track = env->NewObject(track_class, audio_track_init);
+
+ // Load OvkAudioTrack values form fields (class variables)
+ env->SetObjectField(track, codec_name_field, env->NewStringUTF(pCodec->name));
+ env->SetLongField(track, sample_rate_field, pCodecCtx->sample_rate);
+ env->SetLongField(track, bitrate_field, pCodecCtx->bit_rate);
+ env->SetIntField(track, channels_field, pCodecCtx->channels);
+ }
+ } catch (...) {
+ if(debug_mode) {
+ LOGE(1, "[ERROR] Track not found");
+ }
+ return NULL;
+ }
+ return track;
+};
\ No newline at end of file
diff --git a/ndk-modules/ovkmplayer/ovkmplay.cpp b/ndk-modules/ovkmplayer/ovkmplay.cpp
new file mode 100644
index 000000000..792b9ed3a
--- /dev/null
+++ b/ndk-modules/ovkmplayer/ovkmplay.cpp
@@ -0,0 +1,367 @@
+/**
+ * OPENVK LEGACY LICENSE NOTIFICATION
+ *
+ * This file is part of OpenVK Legacy.
+ *
+ * OpenVK Legacy is free software: you can redistribute it and/or modify it under the terms of
+ * the GNU Affero General Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License along with this
+ * program. If not, see https://www.gnu.org/licenses/.
+ *
+ * Source code: https://github.com/openvk/mobile-android-legacy
+ */
+
+// Java/C++ standard implementations headers
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+// Non-standard 'stdint' implementation
+#pragma clang diagnostic push
+#pragma ide diagnostic ignored "OCDFAInspection"
+extern "C"{
+ #ifdef __cplusplus
+ #define __STDC_CONSTANT_MACROS
+ #ifdef _STDINT_H
+ #undef _STDINT_H
+ #endif
+ # include
+ #endif
+}
+#ifndef INT64_C
+#define INT64_C(c) (c ## LL)
+#define UINT64_C(c) (c ## ULL)
+#endif
+
+// Android implementations headers
+#include
+
+
+#include
+#include
+
+/*for Android logs*/
+#define LOG_TAG "OVK-MPLAY-LIB"
+#define LOG_LEVEL 10
+#define LOGD(level, ...) if (level <= LOG_LEVEL) {__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__);}
+#define LOGI(level, ...) if (level <= LOG_LEVEL) {__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__);}
+#define LOGW(level, ...) if (level <= LOG_LEVEL) {__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__);}
+#define LOGE(level, ...) if (level <= LOG_LEVEL) {__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__);}
+
+char version[7] = "0.0.1";
+char *gFileName; // file name of the video
+int gErrorCode;
+
+bool gDebugMode = true;
+
+int gFramesCount,
+ gAttachResult = -54;
+
+FFmpegWrapper *gWrapper;
+
+JavaVM* gVM;
+JavaVMAttachArgs gVMArgs;
+jobject gInstance;
+JNIEnv* gEnv;
+
+jbyteArray jBuffer;
+
+class IPlayerWrapper : public IFFmpegWrapper {
+ public:
+ void onError(int cmdId, int errorCode);
+ void onResult(int cmdId, int resultCode);
+ void onStreamDecoding(uint8_t *buffer,
+ int bufferLen,
+ int streamIndex);
+ void onChangePlaybackState(int playbackState) {};
+ void onChangeWrapperState(int wrapperState);
+ JNIEnv *env;
+ jobject instance;
+};
+
+IPlayerWrapper* gInterface;
+
+int attachEnv(JNIEnv **pEnv) {
+ int getEnvStat = gVM->GetEnv((void **) pEnv, JNI_VERSION_1_6);
+ if (getEnvStat == JNI_EDETACHED) {
+ if (gVM->AttachCurrentThread(pEnv, NULL) != 0) {
+ LOGE(10, "Failed to attach thread with JNIEnv*");
+ return 2; //Failed to attach
+ }
+ return 1; //Attached. Need detach
+ } else if (JNI_OK == getEnvStat) {
+ return 0;//Already attached
+ } else {
+ return 3;
+ }
+}
+
+JNIEXPORT void JNICALL naInit(JNIEnv *env, jobject instance) {
+ gInterface = new IPlayerWrapper();
+ gInterface->instance = env->NewGlobalRef(instance);
+ gWrapper = new FFmpegWrapper(gDebugMode, gInterface);
+}
+
+JNIEXPORT void JNICALL naPlay(JNIEnv *env, jobject instance, int streamType) {
+ gVMArgs.version = JNI_VERSION_1_6;
+ gVMArgs.name = NULL;
+ gVMArgs.group = NULL;
+ gWrapper->setPlaybackState(FFMPEG_PLAYBACK_PLAYING);
+ //gWrapper->startDecoding();
+}
+
+JNIEXPORT void JNICALL naStartAudioDecoding(JNIEnv *env, jobject instance) {
+ gWrapper->startDecoding(gWrapper->gAudioStreamIndex);
+}
+
+JNIEXPORT void JNICALL naStartVideoDecoding(JNIEnv *env, jobject instance) {
+ gWrapper->startDecoding(gWrapper->gVideoStreamIndex);
+}
+
+JNIEXPORT void JNICALL naPause(JNIEnv *env, jobject instance) {
+ gWrapper->setPlaybackState(FFMPEG_PLAYBACK_PAUSED);
+}
+
+JNIEXPORT void JNICALL naStop(JNIEnv *env, jobject instance) {
+ gWrapper->setPlaybackState(FFMPEG_PLAYBACK_STOPPED);
+}
+
+JNIEXPORT jstring JNICALL naShowLogo(JNIEnv *env, jobject instance) {
+ char logo[256] = "Logo";
+ sprintf(logo, "OpenVK Media Player ver. %s for Android"
+ "\r\nOpenVK Media Player for Android is part of OpenVK Legacy Android app "
+ "licensed under AGPLv3 or later version."
+ "\r\nUsing FFmpeg licensed under LGPLv3 or later version.", version);
+ return env->NewStringUTF(logo);
+}
+
+JNIEXPORT void JNICALL naSetDebugMode(JNIEnv *env, jobject instance, jboolean value) {
+ if(gWrapper != NULL) {
+ gWrapper->setDebugMode(value == JNI_TRUE);
+ }
+}
+
+JNIEXPORT jint JNICALL naGetPlaybackState(JNIEnv *env, jobject instance) {
+ return (jint)gWrapper->getPlaybackState();
+}
+
+JNIEXPORT jint JNICALL naOpenFile(JNIEnv *env, jobject instance, jstring filename) {
+ gFileName = (char *)env->GetStringUTFChars(filename, NULL);
+ gWrapper->openInputFile(gFileName, true);
+ return 0;
+}
+
+void IPlayerWrapper::onError(int cmdId, int errorCode) {
+ LOGE(10, "Error Callback Test: %d | %d", cmdId, errorCode);
+}
+
+void IPlayerWrapper::onResult(int cmdId, int resultCode) {
+ JNIEnv* env;
+ int attachResult = attachEnv(&env);
+ if(attachResult < 2) {
+ jclass jmPlay = env->GetObjectClass(instance);
+ if(cmdId == FFMPEG_COMMAND_FIND_STREAMS) {
+ gWrapper->openCodecs();
+ } else if(cmdId == FFMPEG_COMMAND_OPEN_CODECS) {
+ jmethodID onResultMid = env->GetMethodID(jmPlay, "onResult", "(II)V");
+ env->CallVoidMethod(instance, onResultMid, (jint)cmdId, (jint)resultCode);
+ }
+ if(attachResult == 1) {
+ gVM->DetachCurrentThread();
+ }
+ }
+}
+
+void IPlayerWrapper::onStreamDecoding(uint8_t* buffer, int bufferLen, int streamIndex) {
+ JNIEnv* env;
+ int attachResult = attachEnv(&env);
+ if(attachResult < 2) {
+ jclass jmPlay = env->GetObjectClass(instance);
+ jBuffer = env->NewByteArray((jsize) bufferLen);
+ env->SetByteArrayRegion(jBuffer, 0, (jsize) bufferLen, (jbyte *) buffer);
+ if(streamIndex == gWrapper->gAudioStreamIndex) {
+ jmethodID renderAudioMid = env->GetMethodID(jmPlay, "renderAudio", "([BI)V");
+ env->CallVoidMethod(instance, renderAudioMid, jBuffer, bufferLen);
+ } else if(streamIndex == gWrapper->gVideoStreamIndex) {
+ jmethodID renderVideoMid = env->GetMethodID(jmPlay, "renderVideo", "([BI)V");
+ env->CallVoidMethod(instance, renderVideoMid, jBuffer, bufferLen);
+ }
+ env->ReleaseByteArrayElements(jBuffer, (jbyte *)env->GetByteArrayElements(jBuffer, NULL), JNI_ABORT);
+ env->DeleteLocalRef(jBuffer);
+ env->DeleteLocalRef(jmPlay);
+ if(attachResult == 1) {
+ gVM->DetachCurrentThread();
+ }
+ }
+}
+
+void IPlayerWrapper::onChangeWrapperState(int wrapperState) {
+}
+
+JNIEXPORT jobject JNICALL naGenerateTrackInfo(
+ JNIEnv* env, jobject instance, jint type
+) {
+ jclass track_class;
+ try {
+ AVStream *pStream;
+ if (type == 0 && gWrapper->gVideoStreamIndex != -1) {
+ if(gDebugMode) {
+ LOGD(1, "[DEBUG] Searching video stream...");
+ }
+ pStream = gWrapper->getStream(gWrapper->gVideoStreamIndex);
+
+ if(gDebugMode) {
+ LOGD(1, "[DEBUG] Searching video stream... OK");
+ }
+ // Load OvkVideoTrack class
+ track_class = env->FindClass(
+ "uk/openvk/android/legacy/utils/media/OvkVideoTrack"
+ );
+ // Load OvkVideoTrack class method
+ jmethodID video_track_init = env->GetMethodID(
+ track_class, "", "()V"
+ );
+ jfieldID codec_name_field = env->GetFieldID(
+ track_class, "codec_name", "Ljava/lang/String;"
+ );
+ jfieldID frame_size_field = env->GetFieldID(track_class, "frame_size", "[I");
+ jfieldID bitrate_field = env->GetFieldID(
+ track_class, "bitrate", "J"
+ );
+ jfieldID frame_rate_field = env->GetFieldID(
+ track_class, "frame_rate", "F"
+ );
+ jobject track = env->NewObject(track_class, video_track_init);
+
+ // Load OvkVideoTrack values form fields (class variables)
+ env->SetObjectField(track, codec_name_field, env->NewStringUTF(gWrapper->gVideoCodec->name));
+ jintArray array = (jintArray) env->GetObjectField(track, frame_size_field);
+ jint *frame_size = env->GetIntArrayElements(array, 0);
+ frame_size[0] = gWrapper->gVideoCodecCtx->width;
+ frame_size[1] = gWrapper->gVideoCodecCtx->height;
+ env->ReleaseIntArrayElements(array, frame_size, 0);
+ env->SetLongField(track, bitrate_field, gWrapper->gVideoCodecCtx->bit_rate);
+ env->SetFloatField(track, frame_rate_field, pStream->avg_frame_rate.num);
+ return track;
+ } else if(type == 1 && gWrapper->gAudioStreamIndex != -1) {
+ pStream = gWrapper->getStream(gWrapper->gAudioStreamIndex);
+ // Load OvkAudioTrack class
+ track_class = env->FindClass(
+ "uk/openvk/android/legacy/utils/media/OvkAudioTrack"
+ );
+ // Load OvkVideoTrack class methods
+ jmethodID audio_track_init = env->GetMethodID(
+ track_class, "", "()V"
+ );
+
+ jobject track = env->NewObject(track_class, audio_track_init);
+
+ jfieldID codec_name_field = env->GetFieldID(
+ track_class, "codec_name", "Ljava/lang/String;"
+ );
+ jfieldID sample_rate_field = env->GetFieldID(
+ track_class, "sample_rate", "J"
+ );
+ jfieldID bitrate_field = env->GetFieldID(
+ track_class, "bitrate", "J"
+ );
+ jfieldID channels_field = env->GetFieldID(
+ track_class, "channels", "I"
+ );
+
+ // Load OvkAudioTrack values form fields (class variables)
+ env->SetObjectField(track, codec_name_field, env->NewStringUTF(gWrapper->gAudioCodec->name));
+ env->SetLongField(track, sample_rate_field, gWrapper->gAudioCodecCtx->sample_rate);
+ env->SetLongField(track, bitrate_field, gWrapper->gAudioCodecCtx->bit_rate);
+ env->SetIntField(track, channels_field, gWrapper->gAudioCodecCtx->channels);
+ return track;
+ } else {
+ if(gDebugMode) {
+ LOGE(1, "[ERROR] Track not found");
+ }
+ }
+ } catch (...) {
+ if(gDebugMode) {
+ LOGE(1, "[ERROR] Track not found");
+ }
+ return NULL;
+ }
+ return NULL;
+}
+
+jint JNI_OnLoad(JavaVM* pVm, void* reserved) {
+ JNIEnv* env;
+ if (pVm->GetEnv((void **)&env, JNI_VERSION_1_6) != JNI_OK) {
+ return -1;
+ }
+ JNINativeMethod nm[11];
+
+ nm[0].name = "naInit";
+ nm[0].signature = "()V";
+ nm[0].fnPtr = (void*)naInit;
+
+ nm[1].name = "naShowLogo";
+ nm[1].signature = "()Ljava/lang/String;";
+ nm[1].fnPtr = (void*)naShowLogo;
+
+ nm[2].name = "naSetDebugMode";
+ nm[2].signature = "(Z)V";
+ nm[2].fnPtr = (void*)naSetDebugMode;
+
+ nm[3].name = "naOpenFile";
+ nm[3].signature = "(Ljava/lang/String;)I";
+ nm[3].fnPtr = (void*)naOpenFile;
+
+ nm[4].name = "naGenerateTrackInfo";
+ nm[4].signature = "(I)Ljava/lang/Object;";
+ nm[4].fnPtr = (void*)naGenerateTrackInfo;
+
+ nm[5].name = "naGetPlaybackState";
+ nm[5].signature = "()I";
+ nm[5].fnPtr = (void*)naGetPlaybackState;
+
+ nm[6].name = "naPlay";
+ nm[6].signature = "()V";
+ nm[6].fnPtr = (void*)naPlay;
+
+ nm[7].name = "naPause";
+ nm[7].signature = "()V";
+ nm[7].fnPtr = (void*)naPause;
+
+ nm[8].name = "naStop";
+ nm[8].signature = "()V";
+ nm[8].fnPtr = (void*)naStop;
+
+ nm[9].name = "naStartAudioDecoding";
+ nm[9].signature = "()V";
+ nm[9].fnPtr = (void*)naStartAudioDecoding;
+
+ nm[10].name = "naStartVideoDecoding";
+ nm[10].signature = "()V";
+ nm[10].fnPtr = (void*)naStartVideoDecoding;
+
+ jclass cls = env->FindClass("uk/openvk/android/legacy/utils/media/OvkMediaPlayer");
+ //Register methods with env->RegisterNatives.
+ env->RegisterNatives(cls, nm, 11);
+
+ gVM = pVm;
+
+ return JNI_VERSION_1_6;
+}
diff --git a/ndk-modules/ovkmplayer/ovkmplayer.kdev4 b/ndk-modules/ovkmplayer/ovkmplayer.kdev4
new file mode 100644
index 000000000..b1fe94e1d
--- /dev/null
+++ b/ndk-modules/ovkmplayer/ovkmplayer.kdev4
@@ -0,0 +1,4 @@
+[Project]
+CreatedFrom=
+Manager=KDevCustomBuildSystem
+Name=ovkmplayer
diff --git a/ndk-modules/ovkmplayer/utils/android.cpp b/ndk-modules/ovkmplayer/utils/android.cpp
new file mode 100644
index 000000000..2f2acfea0
--- /dev/null
+++ b/ndk-modules/ovkmplayer/utils/android.cpp
@@ -0,0 +1,39 @@
+/**
+ * OPENVK LEGACY LICENSE NOTIFICATION
+ *
+ * This file is part of OpenVK Legacy.
+ *
+ * OpenVK Legacy is free software: you can redistribute it and/or modify it under the terms of
+ * the GNU Affero General Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License along with this
+ * program. If not, see https://www.gnu.org/licenses/.
+ *
+ * Source code: https://github.com/openvk/mobile-android-legacy
+ */
+
+#include
+#include "android.h"
+
+int android::getApiLevel(JNIEnv *env) {
+ bool result = false;
+ jclass versionClass = env->FindClass("android/os/Build$VERSION");
+ if (NULL != versionClass)
+ result = true;
+
+ jfieldID sdkIntFieldID = NULL;
+ if (result)
+ result = (NULL != (sdkIntFieldID = env->GetStaticFieldID(versionClass, "SDK_INT", "I")));
+
+ int version = env->GetStaticIntField(versionClass, sdkIntFieldID);
+ env->DeleteLocalRef(versionClass);
+ if (result) {
+ return version;
+ } else {
+ return -1;
+ }
+}
diff --git a/ndk-modules/ovkmplayer/utils/android.h b/ndk-modules/ovkmplayer/utils/android.h
new file mode 100644
index 000000000..21280cefe
--- /dev/null
+++ b/ndk-modules/ovkmplayer/utils/android.h
@@ -0,0 +1,63 @@
+/**
+ * OPENVK LEGACY LICENSE NOTIFICATION
+ *
+ * This file is part of OpenVK Legacy.
+ *
+ * OpenVK Legacy is free software: you can redistribute it and/or modify it under the terms of
+ * the GNU Affero General Public License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License along with this
+ * program. If not, see https://www.gnu.org/licenses/.
+ *
+ * Source code: https://github.com/openvk/mobile-android-legacy
+ */
+
+#ifndef MOBILE_ANDROID_LEGACY_ANDROID_H
+#define MOBILE_ANDROID_LEGACY_ANDROID_H
+
+#include
+
+// OS codenames ("1.0" - "11" / 1 - 30)
+
+const int OS_CODENAME_A = 1;
+const int OS_CODENAME_B = 2;
+const int OS_CODENAME_CUPCAKE = 3;
+const int OS_CODENAME_DONUT = 4;
+const int OS_CODENAME_ECLAIR = 5;
+const int OS_CODENAME_ECLAIR_2_0_1 = 6;
+const int OS_CODENAME_ECLAIR_2_1 = 7;
+const int OS_CODENAME_FROYO = 8;
+const int OS_CODENAME_GINGERBREAD = 9;
+const int OS_CODENAME_GINGERBREAD_2_3_6 = 10;
+const int OS_CODENAME_HONEYCOMB = 11;
+const int OS_CODENAME_HONEYCOMB_3_1 = 12;
+const int OS_CODENAME_HONEYCOMB_3_2 = 13;
+const int OS_CODENAME_ICS = 14;
+const int OS_CODENAME_ICS_4_0_3 = 15;
+const int OS_CODENAME_JB = 16;
+const int OS_CODENAME_JB_4_2 = 17;
+const int OS_CODENAME_JB_4_3 = 18;
+const int OS_CODENAME_KITKAT = 19;
+const int OS_CODENAME_KITKAT_W = 20;
+const int OS_CODENAME_LOLLIPOP = 21;
+const int OS_CODENAME_LOLLIPOP_5_1 = 22;
+const int OS_CODENAME_MARSHMALLOW = 23;
+const int OS_CODENAME_NOUGAT = 24;
+const int OS_CODENAME_NOUGAT_7_1 = 25;
+const int OS_CODENAME_OREO = 26;
+const int OS_CODENAME_OREO_8_1 = 27;
+const int OS_CODENAME_PIE = 28;
+const int OS_CODENAME_Q = 29;
+const int OS_CODENAME_R = 30;
+
+class android {
+public:
+ static int getApiLevel(JNIEnv *env);
+};
+
+
+#endif //MOBILE_ANDROID_LEGACY_ANDROID_VERSION_H
diff --git a/ndk-modules/ovkmplayer/utils/decthread.cpp b/ndk-modules/ovkmplayer/utils/decthread.cpp
new file mode 100644
index 000000000..2282d1e33
--- /dev/null
+++ b/ndk-modules/ovkmplayer/utils/decthread.cpp
@@ -0,0 +1,57 @@
+//
+// Created by tretdm on 20.04.2024.
+//
+
+#include "decthread.h"
+
+DecoderThread::DecoderThread() {
+ pthread_mutex_init(&mLock, NULL);
+ pthread_cond_init(&mCond, NULL);
+}
+
+DecoderThread::~DecoderThread() {
+
+}
+
+void DecoderThread::start() {
+ handleThread(NULL);
+}
+
+void DecoderThread::startAsync() {
+ pthread_create(&mThread, NULL, startThread, this);
+}
+
+int DecoderThread::wait() {
+ if(!mRunning)
+ {
+ return 0;
+ }
+ return pthread_join(mThread, NULL);
+}
+
+void DecoderThread::stop() {
+}
+
+void* DecoderThread::startThread(void* ptr) {
+ LOGD(10, "Thread starting...");
+ DecoderThread* thread = (DecoderThread *) ptr;
+ thread->mRunning = true;
+ thread->handleThread(ptr);
+ thread->mRunning = false;
+ LOGD(10, "Thread ended.");
+}
+
+void DecoderThread::waitOnNotify() {
+ pthread_mutex_lock(&mLock);
+ pthread_cond_wait(&mCond, &mLock);
+ pthread_mutex_unlock(&mLock);
+}
+
+void DecoderThread::notify() {
+ pthread_mutex_lock(&mLock);
+ pthread_cond_signal(&mCond);
+ pthread_mutex_unlock(&mLock);
+}
+
+void DecoderThread::handleThread(void* ptr) {
+}
diff --git a/ndk-modules/ovkmplayer/utils/decthread.h b/ndk-modules/ovkmplayer/utils/decthread.h
new file mode 100644
index 000000000..72d069788
--- /dev/null
+++ b/ndk-modules/ovkmplayer/utils/decthread.h
@@ -0,0 +1,49 @@
+//
+// Created by tretdm on 20.04.2024.
+//
+
+#ifndef MOBILE_ANDROID_LEGACY_THREAD_H
+#define MOBILE_ANDROID_LEGACY_THREAD_H
+
+#include
+
+extern "C" {
+ #include
+}
+
+#define LOG_TAG "OVK-MPLAY-LIB"
+#define LOG_LEVEL 10
+#define LOGD(level, ...) if (level <= LOG_LEVEL) {__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__);}
+#define LOGI(level, ...) if (level <= LOG_LEVEL) {__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__);}
+#define LOGW(level, ...) if (level <= LOG_LEVEL) {__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__);}
+#define LOGE(level, ...) if (level <= LOG_LEVEL) {__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__);}
+
+class DecoderThread {
+ public:
+ DecoderThread();
+ ~DecoderThread();
+
+ void start();
+ void startAsync();
+ int wait();
+
+ void waitOnNotify();
+ void notify();
+ virtual void stop();
+
+ protected:
+ bool mRunning;
+
+ virtual void handleThread(void* ptr);
+
+ private:
+ pthread_t mThread;
+ pthread_mutex_t mLock;
+ pthread_cond_t mCond;
+
+ static void* startThread(void* ptr);
+};
+
+
+
+#endif //MOBILE_ANDROID_LEGACY_THREAD_H
diff --git a/ndk-modules/ovkmplayer/utils/ffwrap.cpp b/ndk-modules/ovkmplayer/utils/ffwrap.cpp
new file mode 100644
index 000000000..a89c74692
--- /dev/null
+++ b/ndk-modules/ovkmplayer/utils/ffwrap.cpp
@@ -0,0 +1,269 @@
+//
+// Created by tretdm on 20.04.2024.
+//
+
+#include "ffwrap.h"
+
+#include
+#include
+
+FFmpegWrapper::FFmpegWrapper(bool pDebugMode, IFFmpegWrapper *pInterface) {
+ gInterface = pInterface;
+ setDebugMode(pDebugMode);
+ if(pDebugMode) {
+ LOGD(10, "[DEBUG] Initializing FFmpeg...");
+ getAVFormatVersion();
+ getAVFormatLicense();
+ getAVFormatBuildConf();
+
+ }
+ av_register_all();
+ avcodec_register_all();
+ avformat_network_init();
+}
+
+char* FFmpegWrapper::getAVFormatVersion() {
+ char avfVersion[10];
+
+ int avfVersionI = avformat_version();
+ if(gDebugMode) {
+ LOGD(10, "[DEBUG] libavformat version: %d.%d.%d",
+ LIBAVUTIL_VERSION_MAJOR,
+ LIBAVUTIL_VERSION_MINOR,
+ LIBAVUTIL_VERSION_MICRO
+ );
+ }
+ sprintf(avfVersion,
+ "%d.%d.%d",
+ LIBAVUTIL_VERSION_MAJOR,
+ LIBAVUTIL_VERSION_MINOR,
+ LIBAVUTIL_VERSION_MICRO
+ );
+
+ return avfVersion;
+}
+
+char* FFmpegWrapper::getAVFormatBuildConf() {
+ char* originalBuildConf = (char*)av_malloc(2048 * sizeof(char));
+ char* token;
+ char* formattedBuildConf = (char*)av_malloc(2048 * sizeof(char));
+
+ sprintf(originalBuildConf, "%s", avformat_configuration());
+
+ token = strtok(originalBuildConf, " ");
+ int result = 0;
+ int lines = 0;
+ while (token != NULL){
+ if(lines % 8 > 0) {
+ result += sprintf(formattedBuildConf + result, "\t%s\r\n", token);
+ } else {
+ if(gDebugMode) {
+ if(lines == 8) {
+ LOGD(10, "[DEBUG] libavformat build configuration:\r\n%s",
+ formattedBuildConf
+ )
+ } else if(lines > 8) {
+ LOGD(10, "%s",
+ formattedBuildConf
+ )
+ }
+ }
+ result = sprintf(formattedBuildConf, "\t%s\r\n", token);
+ }
+ lines++;
+ token = strtok(NULL, " ");
+ }
+ av_free(originalBuildConf);
+ av_free(formattedBuildConf);
+ return (char*)avformat_configuration();
+}
+
+char* FFmpegWrapper::getAVFormatLicense() {
+ if(gDebugMode) {
+ LOGD(10, "[DEBUG] libavformat license: %s",
+ avformat_license()
+ );
+ }
+ return (char*)avformat_license();
+
+}
+
+void FFmpegWrapper::setDebugMode(bool pDebugMode) {
+ gDebugMode = pDebugMode;
+}
+
+int FFmpegWrapper::getPlaybackState() {
+ return gPlaybackState;
+}
+
+void FFmpegWrapper::setPlaybackState(int pPlaybackState) {
+ gPlaybackState = pPlaybackState;
+ if(gDebugMode) {
+ LOGD(10, "[DEBUG] gPlaybackState now is %d...", gPlaybackState);
+ }
+}
+
+void FFmpegWrapper::openInputFile(char* pFileName, bool afterFindStreams) {
+ int result;
+ char errorString[192] = "No error";
+
+ gFileName = pFileName;
+
+ gFormatCtx = avformat_alloc_context();
+
+ if(gDebugMode) {
+ LOGD(10, "[DEBUG] Opening file %s...", pFileName);
+ }
+
+ if((result = avformat_open_input(&gFormatCtx, pFileName, NULL, NULL))!=0){
+ if(result == -2) {
+ sprintf(errorString, "File not found");
+ } else if (av_strerror(result, errorString, 192) < 0) {
+ strerror_r(-gErrorCode, errorString, 192);
+ }
+ if(gDebugMode) {
+ LOGE(10, "[ERROR] Cannot open file %s (%d, %s)", pFileName, result, errorString);
+ }
+ gInterface->onError(FFMPEG_COMMAND_OPEN_INPUT, result);
+ return;
+ }
+ if(afterFindStreams) {
+ findStreams();
+ } else {
+ gInterface->onResult(FFMPEG_COMMAND_OPEN_INPUT, result);
+ }
+}
+
+void FFmpegWrapper::findStreams() {
+ if(avformat_find_stream_info(gFormatCtx, NULL)<0){
+ if(gDebugMode) {
+ LOGE(10, "[ERROR] Failed to find stream info %s", gFileName);
+ }
+ gInterface->onError(FFMPEG_COMMAND_FIND_STREAMS, -1);
+ }
+
+ av_dump_format(gFormatCtx, 0, gFileName, 0);
+
+ gVideoStreamIndex = -1;
+ for(int i = 0; inb_streams; i++) {
+ if(gFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
+ gVideoStreamIndex = i;
+ } else if(gFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
+ gAudioStreamIndex = i;
+ }
+ }
+
+ if(gVideoStreamIndex == -1){
+ if(gDebugMode) {
+ LOGE(10, "[ERROR] Didn't find a video stream");
+ }
+ } else if(gAudioStreamIndex == -1){
+ if(gDebugMode) {
+ LOGE(10, "[ERROR] Didn't find a audio stream");
+ }
+ } else if(gVideoStreamIndex == -1 && gAudioStreamIndex == -1){
+ if(gDebugMode) {
+ LOGE(10, "[ERROR] Media streams not found");
+ }
+ gInterface->onError(FFMPEG_COMMAND_FIND_STREAMS, -1);
+ } else {
+ gInterface->onResult(FFMPEG_COMMAND_FIND_STREAMS, 0);
+ }
+}
+
+void FFmpegWrapper::openCodecs() {
+ int vCodecResult, aCodecResult;
+ AVDictionary *optionsDict = NULL;
+
+ if(gVideoStreamIndex != -1) {
+ gVideoCodecCtx = getStream(gVideoStreamIndex)->codec;
+ gVideoCodec = avcodec_find_decoder(gVideoCodecCtx->codec_id);
+ if(gVideoCodec==NULL) {
+ LOGE(10, "[ERROR] Unsupported video codec");
+ vCodecResult = -1;
+ } else if(avcodec_open2(gVideoCodecCtx, gVideoCodec, &optionsDict)<0){
+ LOGE(10, "[ERROR] Could not open video codec");
+ vCodecResult = -1;
+ }
+ }
+ if(gAudioStreamIndex != -1) {
+ gAudioCodecCtx = gFormatCtx->streams[gAudioStreamIndex]->codec;
+ gAudioCodec = avcodec_find_decoder(gAudioCodecCtx->codec_id);
+ if(gAudioCodec == NULL) {
+ LOGE(10, "[ERROR] Unsupported audio codec");
+ aCodecResult = -1;
+ } else if(avcodec_open2(gAudioCodecCtx, gAudioCodec, &optionsDict)<0){
+ LOGE(10, "[ERROR] Could not open audio codec");
+ aCodecResult = -1;
+ }
+ }
+
+ if(aCodecResult != -1 || vCodecResult != -1) {
+ gInterface->onResult(FFMPEG_COMMAND_OPEN_CODECS, 0);
+ } else {
+ gInterface->onError(FFMPEG_COMMAND_OPEN_CODECS, -1);
+ }
+}
+
+AVStream* FFmpegWrapper::getStream(int index) {
+ return gFormatCtx->streams[index];
+}
+
+static void *audioDecoderThread(void *arg) {
+ AudioDecoder *audioDec = (AudioDecoder*) arg;
+ audioDec->prepare();
+ audioDec->start();
+}
+
+static void *videoDecoderThread(void *arg) {
+ VideoDecoder *videoDec = (VideoDecoder*) arg;
+ videoDec->prepare();
+ videoDec->start();
+}
+
+void FFmpegWrapper::startDecoding() {
+ AudioDecoder *audioDec = new AudioDecoder(
+ gFormatCtx,
+ gAudioCodecCtx,
+ getStream(gAudioStreamIndex),
+ gAudioStreamIndex,
+ gInterface
+ );
+ VideoDecoder *videoDec = new VideoDecoder(
+ gFormatCtx,
+ gVideoCodecCtx,
+ getStream(gVideoStreamIndex),
+ gVideoStreamIndex,
+ gInterface
+ );
+ pthread_t audioDecThread;
+ pthread_create(&audioDecThread, NULL, &audioDecoderThread, (void*)audioDec);
+ pthread_t videoDecThread;
+ pthread_create(&videoDecThread, NULL, &videoDecoderThread, (void*)videoDec);
+}
+
+void FFmpegWrapper::startDecoding(int pStreamIndex) {
+ if(pStreamIndex == gAudioStreamIndex) {
+ AudioDecoder *audioDec = new AudioDecoder(
+ gFormatCtx,
+ gAudioCodecCtx,
+ getStream(gAudioStreamIndex),
+ gAudioStreamIndex,
+ gInterface
+ );
+
+ pthread_t audioDecThread;
+ pthread_create(&audioDecThread, NULL, &audioDecoderThread, (void*)audioDec);
+ } else if(pStreamIndex == gVideoStreamIndex) {
+ VideoDecoder *videoDec = new VideoDecoder(
+ gFormatCtx,
+ gVideoCodecCtx,
+ getStream(gVideoStreamIndex),
+ gVideoStreamIndex,
+ gInterface
+ );
+
+ pthread_t videoDecThread;
+ pthread_create(&videoDecThread, NULL, &videoDecoderThread, (void*)videoDec);
+ }
+}
\ No newline at end of file
diff --git a/ndk-modules/ovkmplayer/utils/ffwrap.h b/ndk-modules/ovkmplayer/utils/ffwrap.h
new file mode 100644
index 000000000..9baf0bf9f
--- /dev/null
+++ b/ndk-modules/ovkmplayer/utils/ffwrap.h
@@ -0,0 +1,104 @@
+//
+// Created by tretdm on 20.04.2024.
+//
+
+#ifndef MOBILE_ANDROID_LEGACY_FFWRAP_H
+#define MOBILE_ANDROID_LEGACY_FFWRAP_H
+
+#include
+#include
+
+// Non-standard 'stdint' implementation
+#pragma clang diagnostic push
+#pragma ide diagnostic ignored "OCDFAInspection"
+extern "C"{
+ #ifdef __cplusplus
+ #define __STDC_CONSTANT_MACROS
+ #ifdef _STDINT_H
+ #undef _STDINT_H
+ #endif
+ # include
+ #endif
+}
+#ifndef INT64_C
+#define INT64_C(c) (c ## LL)
+#define UINT64_C(c) (c ## ULL)
+#endif
+
+#include <../interfaces/ffwrap.h>
+#include <../decoders/audiodec.h>
+#include <../decoders/videodec.h>
+
+// FFmpeg implementation headers (using LGPLv3.0 model)
+extern "C" {
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+}
+
+/*for Android logs*/
+#define LOG_TAG "FFmpegWrapper"
+#define LOG_LEVEL 10
+#define LOGD(level, ...) if (level <= LOG_LEVEL) {__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__);}
+#define LOGI(level, ...) if (level <= LOG_LEVEL) {__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__);}
+#define LOGW(level, ...) if (level <= LOG_LEVEL) {__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__);}
+#define LOGE(level, ...) if (level <= LOG_LEVEL) {__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__);}
+
+const int FFMPEG_COMMAND_OPEN_INPUT = 0x2000;
+const int FFMPEG_COMMAND_FIND_STREAMS = 0x2001;
+const int FFMPEG_COMMAND_OPEN_CODECS = 0x2002;
+const int FFMPEG_PLAYBACK_ERROR = 0x7fff;
+const int FFMPEG_PLAYBACK_STOPPED = 0x8000;
+const int FFMPEG_PLAYBACK_PLAYING = 0x0801;
+const int FFMPEG_PLAYBACK_PAUSED = 0x8002;
+
+class FFmpegWrapper {
+ public:
+ FFmpegWrapper(bool pDebugMode, IFFmpegWrapper *pInterface);
+
+ AVFormatContext *gFormatCtx;
+ AVCodecContext *gVideoCodecCtx;
+ AVCodecContext *gAudioCodecCtx;
+
+ int gAudioStreamIndex,
+ gVideoStreamIndex;
+
+ AVCodec *gVideoCodec;
+ AVCodec *gAudioCodec;
+
+ char* getAVFormatVersion();
+ char* getAVFormatLicense();
+ char* getAVFormatBuildConf();
+
+ void setDebugMode(bool pDebugMode);
+ int getPlaybackState();
+ void setPlaybackState(int pPlaybackState);
+ void openInputFile(char* pFileName, bool afterFindStreams);
+ void findStreams();
+ AVStream* getStream(int index);
+ void openCodecs();
+ void startDecoding();
+ void startDecoding(int pStreamIndex);
+
+ private:
+ IFFmpegWrapper *gInterface;
+
+ bool gDebugMode;
+ int gPlaybackState,
+ gErrorCode;
+ char* gFileName;
+};
+
+
+
+#endif //MOBILE_ANDROID_LEGACY_FFWRAP_H
diff --git a/ndk-modules/ovkmplayer/utils/pktqueue.cpp b/ndk-modules/ovkmplayer/utils/pktqueue.cpp
new file mode 100644
index 000000000..687fd1868
--- /dev/null
+++ b/ndk-modules/ovkmplayer/utils/pktqueue.cpp
@@ -0,0 +1,121 @@
+//
+// Created by tretdm on 20.04.2024.
+//
+
+#include "pktqueue.h"
+
+PacketQueue::PacketQueue() {
+ pthread_mutex_init(&mLock, NULL);
+ pthread_cond_init(&mCond, NULL);
+ mFirst = NULL;
+ mLast = NULL;
+ mPackets = 0;
+ mSize = 0;
+ mAbortReq = false;
+}
+
+PacketQueue::~PacketQueue()
+{
+ flush();
+ pthread_mutex_destroy(&mLock);
+ pthread_cond_destroy(&mCond);
+}
+
+void PacketQueue::flush() {
+ AVPacketList *pkt, *pkt1;
+
+ pthread_mutex_lock(&mLock);
+
+ for(pkt = mFirst; pkt != NULL; pkt = pkt1) {
+ av_free_packet(&pkt->pkt);
+ av_freep(&pkt);
+ pkt1 = pkt->next;
+ }
+ mLast = NULL;
+ mFirst = NULL;
+ mPackets = 0;
+ mSize = 0;
+
+ pthread_mutex_unlock(&mLock);
+}
+
+int PacketQueue::length() {
+ pthread_mutex_lock(&mLock);
+ int size = mPackets;
+ pthread_mutex_unlock(&mLock);
+ return size;
+}
+
+int PacketQueue::put(AVPacket* pkt) {
+ AVPacketList *pktList;
+
+ if (av_dup_packet(pkt) < 0)
+ return -1;
+
+ pktList = (AVPacketList *) av_malloc(sizeof(AVPacketList));
+ if (!pktList)
+ return -1;
+ pktList->pkt = *pkt;
+ pktList->next = NULL;
+
+ pthread_mutex_lock(&mLock);
+
+ if (!mLast) {
+ mFirst = pktList;
+ } else {
+ mLast->next = pktList;
+ }
+
+ mLast = pktList;
+ mPackets++;
+ mSize += pktList->pkt.size + sizeof(*pktList);
+
+ pthread_cond_signal(&mCond);
+ pthread_mutex_unlock(&mLock);
+
+ return 0;
+}
+
+int PacketQueue::get(AVPacket *pkt, bool block) {
+ AVPacketList *pktList;
+ int ret;
+
+ pthread_mutex_lock(&mLock);
+
+ for(;;) {
+ if (mAbortReq) {
+ ret = -1;
+ break;
+ }
+
+ pktList = mFirst;
+ if (pktList) {
+ mFirst = pktList->next;
+ if (!mFirst)
+ mLast = NULL;
+ mPackets--;
+ mSize -= pktList->pkt.size + sizeof(*pktList);
+ *pkt = pktList->pkt;
+ av_free(pktList);
+ ret = 1;
+ break;
+ } else if (!block) {
+ ret = 0;
+ break;
+ } else {
+ pthread_cond_wait(&mCond, &mLock);
+ }
+ }
+ pthread_mutex_unlock(&mLock);
+ return ret;
+}
+
+void PacketQueue::abort() {
+ pthread_mutex_lock(&mLock);
+ mAbortReq = true;
+ pthread_cond_signal(&mCond);
+ pthread_mutex_unlock(&mLock);
+}
+
+
+
diff --git a/ndk-modules/ovkmplayer/utils/pktqueue.h b/ndk-modules/ovkmplayer/utils/pktqueue.h
new file mode 100644
index 000000000..6e105249e
--- /dev/null
+++ b/ndk-modules/ovkmplayer/utils/pktqueue.h
@@ -0,0 +1,51 @@
+//
+// Created by tretdm on 20.04.2024.
+//
+
+#ifndef MOBILE_ANDROID_LEGACY_PKTQUEUE_H
+#define MOBILE_ANDROID_LEGACY_PKTQUEUE_H
+
+// FFmpeg implementation headers (using LGPLv3.0 model)
+extern "C" {
+ #define __STDC_CONSTANT_MACROS // workaround for compiler
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+ #include
+}
+
+class PacketQueue {
+ public:
+ PacketQueue();
+ ~PacketQueue();
+
+ void flush();
+ int put(AVPacket* pkt);
+
+ int get(AVPacket *pkt, bool block);
+ int length();
+ void abort();
+
+
+ private:
+ AVPacketList* mFirst;
+ AVPacketList* mLast;
+ int mPackets;
+ int mSize;
+ bool mAbortReq;
+ pthread_mutex_t mLock;
+ pthread_cond_t mCond;
+};
+
+
+
+#endif //MOBILE_ANDROID_LEGACY_PKTQUEUE_H