Compare commits

...

72 Commits

Author SHA1 Message Date
Schoumi 9e82b24cf5 Merge pull request 'Bump versions for v1.2 release' (#56) from release-v1.2 into master
Reviewed-on: https://codeberg.org/Schoumi/PeerTubeLive/pulls/56
2024-05-17 09:30:45 +00:00
Schoumi 8b26069bd9 Bump versions 2024-05-17 11:10:14 +02:00
Schoumi 24bcf9e026 Merge pull request 'fix camera feature needed for the app' (#55) from fix_camera_feature into master
Reviewed-on: https://codeberg.org/Schoumi/PeerTubeLive/pulls/55
2024-05-17 09:02:45 +00:00
Schoumi 767d450d75 fix camera feature needed for the app 2024-05-17 11:02:21 +02:00
Schoumi 15ec63cf18 Merge pull request 'rtmp_port_close' (#54) from rtmp_port_close into master
Reviewed-on: https://codeberg.org/Schoumi/PeerTubeLive/pulls/54
2024-05-17 08:30:21 +00:00
Schoumi 65a8b99dae Merge branch 'master' into rtmp_port_close 2024-05-17 08:29:37 +00:00
Schoumi 611ac54510 ammend FAQ.md 2024-05-17 10:28:04 +02:00
Schoumi 375a2daef8 init some kind of a FAQ 2024-05-17 10:24:21 +02:00
Schoumi 0ed019bb34 Merge pull request 'Fix Themes:' (#53) from fix-dark-theme into master
Reviewed-on: https://codeberg.org/Schoumi/PeerTubeLive/pulls/53
2024-05-17 06:39:16 +00:00
Schoumi e19a0061ae Fix Themes:
- Theme Light on Stream
- Fixed color on Stream
- Logo background removed
2024-05-17 08:37:16 +02:00
Schoumi 2280a40e6e Merge pull request 'Handle 2FA for instance user (#48)' (#52) from 2FA into master
Reviewed-on: https://codeberg.org/Schoumi/PeerTubeLive/pulls/52
2024-05-16 19:23:48 +00:00
Schoumi 693c6088d1 Handle 2FA for instance user (#48) 2024-05-16 21:22:36 +02:00
Schoumi c56f33b7ab Merge pull request 'remove idea files from tracking' (#50) from remove_idea into master
Reviewed-on: https://codeberg.org/Schoumi/PeerTubeLive/pulls/50
2024-04-17 16:16:23 +00:00
Schoumi 63afc87b36 remove idea files from tracking 2024-04-17 18:14:35 +02:00
Schoumi f55b579139 Merge pull request 'Add Replay Settings support' (#49) from replay_5_2 into master
Add Replay Settings support
Change forms-data to json format in live stream initiation

Reviewed-on: https://codeberg.org/Schoumi/PeerTubeLive/pulls/49
2024-04-17 16:10:57 +00:00
Schoumi 7c4850a1ea Merge branch 'master' into replay_5_2 2024-04-17 16:10:19 +00:00
Schoumi 8b509953ca change steam options from forms-data to json 2024-04-17 18:06:35 +02:00
Schoumi 517c6a043a Merge pull request 'German and Polish translations' (#44) from translates into master
Reviewed-on: https://codeberg.org/Schoumi/PeerTubeLive/pulls/44
2023-08-15 19:23:48 +00:00
Schoumi 53f16d1ad7 remove old unusable translation => exist on weblate 2023-08-15 21:03:19 +02:00
Schoumi d93f4b5c84 Translated using Weblate (Polish)
Currently translated at 99.6% (277 of 278 strings)

Translation: PeerTube Live/App
Translate-URL: https://hosted.weblate.org/projects/peertube-live/app/pl/
2023-08-15 20:50:00 +02:00
Mi Klo 58f68d1797 Translated using Weblate (Polish)
Currently translated at 100.0% (2 of 2 strings)

Translation: PeerTube Live/Store
Translate-URL: https://hosted.weblate.org/projects/peertube-live/store/pl/
2023-08-15 20:49:43 +02:00
Mi Klo ccf0eb80db Translated using Weblate (Polish)
Currently translated at 100.0% (278 of 278 strings)

Translation: PeerTube Live/App
Translate-URL: https://hosted.weblate.org/projects/peertube-live/app/pl/
2023-08-15 20:49:23 +02:00
Mi Klo cc782668c5 Added translation using Weblate (Polish) 2023-08-15 20:49:08 +02:00
Felix Tymcik 1839e91896 Translated using Weblate (German)
Currently translated at 100.0% (278 of 278 strings)

Translation: PeerTube Live/App
Translate-URL: https://hosted.weblate.org/projects/peertube-live/app/de/
2023-08-15 20:48:16 +02:00
J. Lavoie cfee1af487 Translated using Weblate (German)
Currently translated at 64.0% (178 of 278 strings)

Translation: PeerTube Live/App
Translate-URL: https://hosted.weblate.org/projects/peertube-live/app/de/
2023-08-15 20:47:49 +02:00
Schoumi 06ab71610f Merge pull request 'rtmp_update' (#40) from rtmp_update into master
Reviewed-on: https://codeberg.org/Schoumi/PeerTubeLive/pulls/40
2022-10-04 17:52:05 +02:00
Schoumi cfffa2e418 Fix some rotation issue 2022-10-04 17:31:53 +02:00
Schoumi 3abf6c3554 Update ci to woodpecker 2022-07-14 13:54:53 +02:00
Schoumi 556bca4622 Update ci to woodpecker 2022-07-14 13:38:36 +02:00
Schoumi 82dae368e0 fix stream orientation 2022-07-14 13:33:30 +02:00
Schoumi b0dace54d7 Upgrade RTMP-RTSP from 1.8.2 (custom) to 2.1.9 (standard)
-> breaks orientation at the moment
Upgrade gradle plugin version
2022-06-16 12:34:41 +02:00
Schoumi 1709bc3688 Merge pull request 'Completed translation' (#39) from translates into master
Reviewed-on: https://codeberg.org/Schoumi/PeerTubeLive/pulls/39
2022-06-15 13:38:15 +02:00
Xosé M 2073a12bbb Translated using Weblate (Galician)
Currently translated at 100.0% (278 of 278 strings)

Translation: PeerTube Live/App
Translate-URL: https://hosted.weblate.org/projects/peertube-live/app/gl/
2022-03-29 18:23:14 +02:00
Xosé M e8bc1ffc92 Translated using Weblate (Galician)
Currently translated at 100.0% (2 of 2 strings)

Translation: PeerTube Live/Store
Translate-URL: https://hosted.weblate.org/projects/peertube-live/store/gl/
2022-03-29 18:23:14 +02:00
Weblate 3e58b85ed7 Added translation using Weblate (Galician) 2022-03-29 18:23:14 +02:00
Ihor Hordiichuk 93557ae115 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (2 of 2 strings)

Translation: PeerTube Live/Store
Translate-URL: https://hosted.weblate.org/projects/peertube-live/store/uk/
2022-03-29 18:05:34 +02:00
Ihor Hordiichuk 81a7401188 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (278 of 278 strings)

Translation: PeerTube Live/App
Translate-URL: https://hosted.weblate.org/projects/peertube-live/app/uk/
2022-03-29 18:05:34 +02:00
Ihor Hordiichuk c559539485 Added translation using Weblate (Ukrainian) 2022-03-29 18:05:34 +02:00
Jiří Podhorecký d127ebff52 Translated using Weblate (Czech)
Currently translated at 100.0% (2 of 2 strings)

Translation: PeerTube Live/Store
Translate-URL: https://hosted.weblate.org/projects/peertube-live/store/cs/
2022-03-29 17:23:54 +02:00
Jiří Podhorecký fefaf147d3 Translated using Weblate (Czech)
Currently translated at 100.0% (278 of 278 strings)

Translation: PeerTube Live/App
Translate-URL: https://hosted.weblate.org/projects/peertube-live/app/cs/
2022-03-29 17:23:54 +02:00
Jiří Podhorecký 3548381019 Translated using Weblate (Czech)
Currently translated at 7.1% (20 of 278 strings)

Translation: PeerTube Live/App
Translate-URL: https://hosted.weblate.org/projects/peertube-live/app/cs/
2022-03-29 17:23:54 +02:00
Jiří Podhorecký c48dd8b207 Added translation using Weblate (Czech) 2022-03-29 17:23:54 +02:00
Balázs Meskó 295a4dcea4 Translated using Weblate (Hungarian)
Currently translated at 100.0% (2 of 2 strings)

Translation: PeerTube Live/Store
Translate-URL: https://hosted.weblate.org/projects/peertube-live/store/hu/
2022-03-29 16:57:59 +02:00
Balázs Meskó 67d7453886 Translated using Weblate (Hungarian)
Currently translated at 99.6% (277 of 278 strings)

Translation: PeerTube Live/App
Translate-URL: https://hosted.weblate.org/projects/peertube-live/app/hu/
2022-03-29 16:57:59 +02:00
Balázs Meskó 131c813710 Added translation using Weblate (Hungarian) 2022-03-29 16:57:59 +02:00
badlop 271ed841d8 Translated using Weblate (Spanish)
Currently translated at 100.0% (278 of 278 strings)

Translation: PeerTube Live/App
Translate-URL: https://hosted.weblate.org/projects/peertube-live/app/es/
2022-03-29 16:14:41 +02:00
Discapacidad cinco 8b01d8557b Translated using Weblate (Spanish)
Currently translated at 100.0% (2 of 2 strings)

Translation: PeerTube Live/Store
Translate-URL: https://hosted.weblate.org/projects/peertube-live/store/es/
2022-03-29 16:14:41 +02:00
Discapacidad cinco db6fd2648b Translated using Weblate (Spanish)
Currently translated at 99.6% (277 of 278 strings)

Translation: PeerTube Live/App
Translate-URL: https://hosted.weblate.org/projects/peertube-live/app/es/
2022-03-29 16:14:41 +02:00
Discapacidad cinco 1d5a2c1682 Added translation using Weblate (Spanish) 2022-03-29 16:14:41 +02:00
J. Lavoie f26d9c4bf4 Translated using Weblate (French)
Currently translated at 100.0% (278 of 278 strings)

Translation: PeerTube Live/App
Translate-URL: https://hosted.weblate.org/projects/peertube-live/app/fr/
2022-03-29 15:57:37 +02:00
Schoumi 736d2aee23 Merge pull request 'Mise à jour de 'README.md'' (#31) from Poussinou/PeerTubeLive:master into master
Reviewed-on: https://codeberg.org/Schoumi/PeerTubeLive/pulls/31
2022-03-28 19:37:25 +02:00
Poussinou 8e570295d9 Mise à jour de 'README.md' 2021-12-05 14:20:59 +01:00
Schoumi 186396dda1 Merge pull request 'put back incomplete translations' (#23) from translates into master
Reviewed-on: https://codeberg.org/Schoumi/PeerTubeLive/pulls/23
2021-10-19 14:03:47 +02:00
Schoumi edef1be85f put back incomplete translations 2021-10-19 14:03:06 +02:00
Schoumi 0c63ad695d Merge pull request 'release v1.1' (#22) from release_1.1 into master
Reviewed-on: https://codeberg.org/Schoumi/PeerTubeLive/pulls/22
2021-10-19 13:29:15 +02:00
Schoumi e668c5b6e5 release-v1.1 2021-10-19 13:11:50 +02:00
Schoumi aaee5998ef Remove uncomplete translation for release 2021-10-19 13:11:30 +02:00
Schoumi f3cf8f6412 Merge pull request 'Fix permission flow' (#21) from fix-permission into master
Reviewed-on: https://codeberg.org/Schoumi/PeerTubeLive/pulls/21
2021-10-19 11:32:08 +02:00
Schoumi f13a8236c7 - Fix crash when user click on button when no permission were given
- Fix live abort when user goto settings to enable permissions
2021-10-18 18:11:54 +02:00
Schoumi 097d8f73c2 Merge pull request 'translations' (#20) from translations into master
Reviewed-on: https://codeberg.org/Schoumi/PeerTubeLive/pulls/20
2021-10-18 11:04:46 +02:00
Hosted Weblate 8ab2253a11
Merge branch 'origin/master' into Weblate. 2021-10-13 16:08:11 +02:00
Schoumi 6450117e20 Merge pull request 'Add translation removed during release process' (#19) from translates into master
Reviewed-on: https://codeberg.org/Schoumi/PeerTubeLive/pulls/19
2021-10-13 16:08:10 +02:00
Schoumi 4d5a83575d Merge branch 'master' into translates 2021-10-13 16:08:01 +02:00
Schoumi 608f68d0a8 Add translation removed during release process 2021-10-13 15:55:31 +02:00
Selyan Sliman Amiri e81f3a1d00
Translated using Weblate (Kabyle)
Currently translated at 1.0% (3 of 278 strings)

Translation: PeerTube Live/App
Translate-URL: https://hosted.weblate.org/projects/peertube-live/app/kab/
2021-10-08 15:16:47 +02:00
Selyan Sliman Amiri e71692c4be
Added translation using Weblate (Kabyle) 2021-10-07 14:22:19 +02:00
Hosted Weblate 029b3ec65b
Merge branch 'origin/master' into Weblate. 2021-09-27 10:10:57 +02:00
Schoumi f461f4b7ad Merge pull request 'Privacy Policy' (#18) from policy into master
Reviewed-on: https://codeberg.org/Schoumi/PeerTubeLive/pulls/18
2021-09-27 10:10:55 +02:00
Schoumi d0e99e91df Privacy Policy 2021-09-27 10:09:53 +02:00
Hosted Weblate 2c24dcd40d
Merge branch 'origin/master' into Weblate. 2021-09-23 15:02:17 +02:00
Hosted Weblate 4e08d29996
Merge branch 'origin/master' into Weblate. 2021-09-23 14:58:48 +02:00
Olexii Ondrei 868d41b20d
Added translation using Weblate (Karelian) 2021-09-22 20:09:14 +02:00
403 changed files with 22960 additions and 9885 deletions

View File

@ -1,9 +0,0 @@
---
kind: pipeline
type: exec
name: default
steps:
- name: build
commands:
- bash ./gradlew assembleDebug

7
.gitignore vendored
View File

@ -1,12 +1,7 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.idea
.DS_Store
/build
/captures

View File

@ -1,65 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectInspectionProfilesVisibleTreeState">
<entry key="Project Default">
<profile-state>
<expanded-state>
<State>
<id />
</State>
<State>
<id>AccessibilityLintAndroid</id>
</State>
<State>
<id>Android</id>
</State>
<State>
<id>Bidirectional TextInternationalizationLintAndroid</id>
</State>
<State>
<id>Chrome OSCorrectnessLintAndroid</id>
</State>
<State>
<id>Class structureJava</id>
</State>
<State>
<id>CorrectnessLintAndroid</id>
</State>
<State>
<id>IconsUsabilityLintAndroid</id>
</State>
<State>
<id>InternationalizationLintAndroid</id>
</State>
<State>
<id>Java</id>
</State>
<State>
<id>Javadoc issuesJava</id>
</State>
<State>
<id>LintAndroid</id>
</State>
<State>
<id>LintLintAndroid</id>
</State>
<State>
<id>MessagesCorrectnessLintAndroid</id>
</State>
<State>
<id>PerformanceLintAndroid</id>
</State>
<State>
<id>SecurityLintAndroid</id>
</State>
<State>
<id>TypographyUsabilityLintAndroid</id>
</State>
<State>
<id>UsabilityLintAndroid</id>
</State>
</expanded-state>
</profile-state>
</entry>
</component>
</project>

3
.idea/.gitignore vendored
View File

@ -1,3 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml

View File

@ -1 +0,0 @@
Peertube Live

View File

@ -1,139 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<option name="RIGHT_MARGIN" value="150" />
<JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value>
<package name="java.util" alias="false" withSubpackages="false" />
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
<package name="io.ktor" alias="false" withSubpackages="true" />
</value>
</option>
<option name="PACKAGES_IMPORT_LAYOUT">
<value>
<package name="" alias="false" withSubpackages="true" />
<package name="java" alias="false" withSubpackages="true" />
<package name="javax" alias="false" withSubpackages="true" />
<package name="kotlin" alias="false" withSubpackages="true" />
<package name="" alias="true" withSubpackages="true" />
</value>
</option>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
</component>

View File

@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" />
</component>
</project>

View File

@ -1,8 +0,0 @@
<component name="ProjectDictionaryState">
<dictionary name="schoumi">
<words>
<w>datas</w>
<w>rtmp</w>
</words>
</dictionary>
</component>

View File

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleHome" value="$APPLICATION_HOME_DIR$/gradle/gradle-2.2.1" />
<option name="gradleJvm" value="1.8" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
<option value="$PROJECT_DIR$/encoder" />
<option value="$PROJECT_DIR$/rtmp" />
<option value="$PROJECT_DIR$/rtplibrary" />
<option value="$PROJECT_DIR$/rtsp" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>

View File

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="BintrayJCenter" />
<option name="name" value="BintrayJCenter" />
<option name="url" value="https://jcenter.bintray.com/" />
</remote-repository>
<remote-repository>
<option name="id" value="Google" />
<option name="name" value="Google" />
<option name="url" value="https://dl.google.com/dl/android/maven2/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven" />
<option name="name" value="maven" />
<option name="url" value="https://jitpack.io" />
</remote-repository>
</component>
</project>

View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CMakeSettings">
<configurations>
<configuration PROFILE_NAME="Debug" CONFIG_NAME="Debug" />
</configurations>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

9
.woodpecker.yml Normal file
View File

@ -0,0 +1,9 @@
pipeline:
build:
image: eclipse-temurin:11
commands:
- bash ./gradlew assembleDebug
environment:
- ANDROID_HOME=/mnt/sdk
volumes:
- /home/woodpecker/sdk:/mnt/sdk

6
FAQ.md Normal file
View File

@ -0,0 +1,6 @@
# The stream doesn't start and display: "Your livestream ended because of a network problem"
If you're the instance administrators, check that the rtmp/rtmps port are open on your server. By default rtmp 1935, rtmps 1936. Check your production.yaml.
If you're not the adminstrators, ask them if the configuration is ok, before reporting a bug.

4
PRIVACY_POLICY Normal file
View File

@ -0,0 +1,4 @@
The app itself does not collect any data from user.
The app connect to your peertube instance account.
The app stream camera and audio data to the account you have connected to.
Please check the privacy policy of your peertube instance to know what the service do with your data.

View File

@ -11,6 +11,13 @@ Stream your phone camera to a PeerTube instance with this Android app.
PeerTube, developped by [Framasoft](https://framasoft.org), is the libre and decentralized alternative to video platforms. Join PeerTube at [https://joinpeertube.org](https://joinpeertube.org)
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
alt="Get it on F-Droid"
height="80">](https://f-droid.org/packages/fr.mobdev.peertubelive/)
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png"
alt="Get it on Google Play"
height="80">](https://play.google.com/store/apps/details?id=fr.mobdev.peertubelive)
## Licence
The app itself is licensed AGPLv3+

View File

@ -5,21 +5,15 @@ apply plugin: 'kotlin-kapt'
apply plugin: 'androidx.navigation.safeargs.kotlin'
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
compileSdkVersion 34
defaultConfig {
applicationId "fr.mobdev.peertubelive"
minSdkVersion 21
targetSdkVersion 30
versionCode 1
versionName "1.0"
targetSdkVersion 34
versionCode 3
versionName "1.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables
{
useSupportLibrary true
}
}
buildTypes {
@ -29,16 +23,18 @@ android {
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = '1.8'
jvmTarget = '17'
}
buildFeatures {
dataBinding true
buildConfig true
}
namespace 'fr.mobdev.peertubelive'
}
@ -46,14 +42,14 @@ dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation project(':rtplibrary')
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
implementation 'androidx.core:core-ktx:1.13.1'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.navigation:navigation-fragment-ktx:2.7.7'
implementation 'androidx.navigation:navigation-ui-ktx:2.7.7'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}

View File

@ -1,13 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="fr.mobdev.peertubelive">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature
android:name="android.hardware.camera"
android:required="true" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
<application
android:allowBackup="true"
@ -19,15 +20,14 @@
<activity
android:name="fr.mobdev.peertubelive.activity.MainActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme">
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="fr.mobdev.peertubelive.activity.StreamActivity"
android:theme="@style/AppTheme.Light" android:screenOrientation="portrait"/>
<activity android:name="fr.mobdev.peertubelive.activity.StreamActivity" android:screenOrientation="portrait"/>
<activity android:name="fr.mobdev.peertubelive.activity.CreateLiveActivity" />
</application>

View File

@ -126,7 +126,7 @@ class CreateLiveActivity : AppCompatActivity() {
updateView(null)
}
override fun onError(error: String?) {
override fun onError(code: String?, error: String?) {
inError = true
updateView(error)
}
@ -150,7 +150,7 @@ class CreateLiveActivity : AppCompatActivity() {
updateView(null)
}
override fun onError(error: String?) {
override fun onError(code: String?, error: String?) {
inError = true
updateView(error)
}
@ -166,7 +166,7 @@ class CreateLiveActivity : AppCompatActivity() {
updateView(null)
}
override fun onError(error: String?) {
override fun onError(code: String?, error: String?) {
inError = true
updateView(error)
}
@ -190,7 +190,7 @@ class CreateLiveActivity : AppCompatActivity() {
updateView(null)
}
override fun onError(error: String?) {
override fun onError(code: String?, error: String?) {
inError = true
updateView(error)
}
@ -214,7 +214,7 @@ class CreateLiveActivity : AppCompatActivity() {
updateView(null)
}
override fun onError(error: String?) {
override fun onError(code: String?, error: String?) {
inError = true
updateView(error)
}
@ -238,7 +238,7 @@ class CreateLiveActivity : AppCompatActivity() {
updateView(null)
}
override fun onError(error: String?) {
override fun onError(code: String?, error: String?) {
inError = true
updateView(error)
}
@ -338,7 +338,7 @@ class CreateLiveActivity : AppCompatActivity() {
}
}
override fun onError(error: String?) {
override fun onError(code: String?, error: String?) {
runOnUiThread {
binding.error.text = error
binding.error.visibility = View.VISIBLE

View File

@ -32,12 +32,14 @@ import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.databinding.DataBindingUtil
import com.pedro.encoder.input.video.CameraHelper
import com.pedro.rtmp.utils.ConnectCheckerRtmp
import com.pedro.rtplibrary.rtmp.RtmpCamera2
import net.ossrs.rtmp.ConnectCheckerRtmp
import fr.mobdev.peertubelive.R
import fr.mobdev.peertubelive.databinding.StreamBinding
import fr.mobdev.peertubelive.manager.InstanceManager.EXTRA_DATA
import fr.mobdev.peertubelive.objects.StreamData
import java.util.*
import kotlin.collections.ArrayList
class StreamActivity : AppCompatActivity() {
@ -50,10 +52,11 @@ class StreamActivity : AppCompatActivity() {
private var surfaceInit: Boolean = false
private var permissionGiven: Boolean = false
private var streamIsActive: Boolean = false
private var screenOrientation: Int = 0
private var screenOrientation: Int = -1
private var lastScreenOrientation: Int = 0
private var orientationCounter: Int = 0
private var rotationIsLanternEnabled: Boolean = true
private var rotationIsEnabled: Boolean = true
private var orientationTimer: Timer = Timer()
companion object {
const val BACKGROUND :Int = 1
@ -69,46 +72,11 @@ class StreamActivity : AppCompatActivity() {
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
binding = DataBindingUtil.setContentView(this, R.layout.stream)
orientationEventListener = object: OrientationEventListener(this, SensorManager.SENSOR_DELAY_NORMAL){
override fun onOrientationChanged(orientation: Int) {
if(orientation < 0 || !rotationIsLanternEnabled)
return
var localOrientation: Int
localOrientation = when (orientation) {
in 45..135 -> {
90
}
in 135..225 -> {
180
}
in 225..315 -> {
270
}
else -> {
0
}
}
if(localOrientation != lastScreenOrientation) {
lastScreenOrientation = localOrientation
orientationCounter = 0
} else {
orientationCounter++
}
if (lastScreenOrientation != screenOrientation && orientationCounter > 30) {
screenOrientation = lastScreenOrientation
rtmpCamera2.glInterface.setStreamRotation(screenOrientation)
if (screenOrientation == 90) {
localOrientation = 270
} else if(screenOrientation == 270) {
localOrientation = 90
}
binding.flash.rotation = localOrientation.toFloat()
binding.muteMicro.rotation = localOrientation.toFloat()
binding.switchCamera.rotation = localOrientation.toFloat()
}
handlerOrientation(orientation)
}
}
orientationEventListener.enable()
@ -119,6 +87,7 @@ class StreamActivity : AppCompatActivity() {
askStopLive(STOP)
}
binding.switchCamera.setOnClickListener { rtmpCamera2.switchCamera() }
binding.switchCamera.visibility = View.GONE
binding.muteMicro.setOnClickListener {
if (rtmpCamera2.isAudioMuted) {
rtmpCamera2.enableAudio()
@ -129,6 +98,7 @@ class StreamActivity : AppCompatActivity() {
binding.muteMicro.setImageResource(R.drawable.baseline_volume_off_24)
}
}
binding.muteMicro.visibility = View.GONE
binding.flash.setOnClickListener {
if (rtmpCamera2.isLanternEnabled) {
rtmpCamera2.disableLantern()
@ -139,17 +109,16 @@ class StreamActivity : AppCompatActivity() {
binding.flash.setImageResource(R.drawable.baseline_flash_on_24)
}
}
binding.flash.visibility = View.GONE
binding.rotation.setOnClickListener {
if (rotationIsLanternEnabled) {
rotationIsLanternEnabled = !rotationIsLanternEnabled
if (rotationIsEnabled)
binding.rotation.setImageResource(R.drawable.baseline_screen_lock_rotation_24)
}
else {
rotationIsLanternEnabled = !rotationIsLanternEnabled
else
binding.rotation.setImageResource(R.drawable.baseline_screen_rotation_24)
}
rotationIsEnabled = !rotationIsEnabled
}
binding.rotation.visibility = View.GONE
binding.surfaceView.holder.addCallback(object: SurfaceHolder.Callback {
override fun surfaceCreated(p0: SurfaceHolder) {
surfaceInit = true
@ -208,6 +177,7 @@ class StreamActivity : AppCompatActivity() {
binding.permissionInfo.visibility = View.VISIBLE
binding.gotoPermission.visibility = View.VISIBLE
binding.surfaceView.visibility = View.GONE
binding.gotoPermission.setOnClickListener {
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
intent.data = Uri.fromParts("package", packageName, null)
@ -217,6 +187,7 @@ class StreamActivity : AppCompatActivity() {
ActivityCompat.requestPermissions(this, permissions.toTypedArray(), 1)
}
} else {
binding.surfaceView.visibility = View.VISIBLE
permissionGiven = true
if(surfaceInit && !streamIsActive)
startStream()
@ -225,7 +196,7 @@ class StreamActivity : AppCompatActivity() {
override fun onStop() {
super.onStop()
if (!hasWindowFocus()) {
if (this@StreamActivity::rtmpCamera2.isInitialized && !hasWindowFocus()) {
unregisterReceiver(lockReceiver)
setResult(BACKGROUND)
finish()
@ -254,13 +225,67 @@ class StreamActivity : AppCompatActivity() {
}
}
private fun handlerOrientation(orientation: Int) {
if(orientation < 0 || !rotationIsEnabled) {
return
}
var localOrientation: Int
localOrientation = when (orientation) {
in 45..135 -> {
90
}
in 135..225 -> {
180
}
in 225..315 -> {
270
}
else -> {
0
}
}
if(localOrientation != lastScreenOrientation) {
lastScreenOrientation = localOrientation
orientationTimer.cancel()
orientationTimer.purge()
orientationTimer = Timer()
orientationTimer.schedule(object : TimerTask() {
override fun run() {
if (lastScreenOrientation != screenOrientation) {
screenOrientation = lastScreenOrientation
rtmpCamera2.glInterface.setStreamRotation(screenOrientation)
if (screenOrientation == 90) {
localOrientation = 270
} else if(screenOrientation == 270) {
localOrientation = 90
}
binding.flash.rotation = localOrientation.toFloat()
binding.muteMicro.rotation = localOrientation.toFloat()
binding.switchCamera.rotation = localOrientation.toFloat()
binding.rotation.rotation = localOrientation.toFloat()
}
}
},3000)
}
}
private fun startStream() {
streamIsActive = true
binding.permissionInfo.visibility = View.GONE
binding.gotoPermission.visibility = View.GONE
binding.surfaceView.visibility = View.VISIBLE
binding.muteMicro.visibility = View.VISIBLE
binding.rotation.visibility = View.VISIBLE
binding.switchCamera.visibility = View.VISIBLE
binding.flash.visibility = View.VISIBLE
val connectChecker : ConnectCheckerRtmp = object : ConnectCheckerRtmp {
override fun onConnectionStartedRtmp(rtmpUrl: String) {
}
override fun onConnectionSuccessRtmp() {
runOnUiThread {
Toast.makeText(binding.root.context, "Connection success", Toast.LENGTH_SHORT).show();
@ -331,16 +356,19 @@ class StreamActivity : AppCompatActivity() {
width = 480
height = 360
}
null -> {
width = 1920
height = 1080
}
}
rtmpCamera2.startPreview(CameraHelper.Facing.BACK, width,height)
//start stream
if (rtmpCamera2.prepareAudio() && rtmpCamera2.prepareVideo(width,height,30, (width*height*30*0.076).toInt(),false,CameraHelper.getCameraOrientation(this))) {
if (rtmpCamera2.prepareAudio() && rtmpCamera2.prepareVideo(width,height,30, (width*height*30*0.076).toInt(),CameraHelper.getCameraOrientation(this),true)) {
println("peertubeurl "+streamData.url+"/"+streamData.key)
rtmpCamera2.startStream(streamData.url+"/"+streamData.key)
} else {
/**This device cant init encoders, this could be for 2 reasons: The encoder selected doesnt support any configuration setted or your device hasnt a H264 or AAC encoder (in this case you can see log error valid encoder not found) */

View File

@ -20,6 +20,7 @@ import android.os.Looper
import android.view.LayoutInflater
import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.DialogFragment
import fr.mobdev.peertubelive.R
@ -45,11 +46,15 @@ class AddInstanceDialog : DialogFragment() {
builder.setPositiveButton(R.string.connect, null)
builder.setNegativeButton(R.string.cancel) { dialog,_ -> dialog.dismiss() }
builder.setView(binding.root)
isCancelable = false
binding.errorUsername.visibility = View.GONE
binding.errorInstance.visibility = View.GONE
binding.errorPassword.visibility = View.GONE
binding.tryConnect.visibility = View.GONE
binding.tryConnectMsg.visibility = View.GONE
binding.twoFaTitle.visibility = View.GONE
binding.twoFa.visibility = View.GONE
binding.errorTwoFa.visibility = View.GONE
if (this::oAuthData.isInitialized)
{
@ -67,9 +72,11 @@ class AddInstanceDialog : DialogFragment() {
val username = binding.username.text.toString()
val password = binding.password.text.toString()
var instance = binding.instance.text.toString()
var twoFaToken = binding.twoFa.text.toString()
binding.errorUsername.visibility = View.GONE
binding.errorInstance.visibility = View.GONE
binding.errorPassword.visibility = View.GONE
binding.errorTwoFa.visibility = View.GONE
binding.error.visibility = View.GONE
var inError = false
if(username.isEmpty())
@ -100,6 +107,13 @@ class AddInstanceDialog : DialogFragment() {
inError = true
}
}
if(twoFaToken.isEmpty() && binding.twoFa.isVisible)
{
binding.errorTwoFa.visibility = View.VISIBLE
inError = true
}
if (!this::oAuthData.isInitialized && DatabaseManager.existsCredential(requireContext(),instance,username)) {
inError = true
binding.error.visibility = View.VISIBLE
@ -109,13 +123,16 @@ class AddInstanceDialog : DialogFragment() {
binding.errorUsername.visibility = View.GONE
binding.errorInstance.visibility = View.GONE
binding.errorPassword.visibility = View.GONE
binding.errorTwoFa.visibility = View.GONE
binding.error.visibility = View.GONE
binding.username.visibility = View.GONE
binding.password.visibility = View.GONE
binding.instance.visibility = View.GONE
binding.twoFa.visibility = View.GONE
binding.usernameTitle.visibility = View.GONE
binding.passwordTitle.visibility = View.GONE
binding.instanceTitle.visibility = View.GONE
binding.twoFaTitle.visibility = View.GONE
binding.tryConnect.visibility = View.VISIBLE
binding.tryConnectMsg.visibility = View.VISIBLE
@ -135,20 +152,54 @@ class AddInstanceDialog : DialogFragment() {
}
}
override fun onError(error: String?) {
override fun onError(code: String?, error: String?) {
Handler(Looper.getMainLooper()).post {
binding.error.visibility = View.VISIBLE
binding.tryConnect.visibility = View.GONE
binding.tryConnectMsg.visibility = View.GONE
binding.username.visibility = View.VISIBLE
binding.password.visibility = View.VISIBLE
binding.instance.visibility = View.VISIBLE
binding.usernameTitle.visibility = View.VISIBLE
binding.passwordTitle.visibility = View.VISIBLE
binding.instanceTitle.visibility = View.VISIBLE
binding.error.text = error
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = true
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).isEnabled = true
if (code.equals("missing_two_factor") || code.equals("invalid_two_factor")) {
binding.error.visibility = View.GONE
binding.tryConnect.visibility = View.GONE
binding.tryConnectMsg.visibility = View.GONE
binding.username.visibility = View.GONE
binding.password.visibility = View.GONE
binding.instance.visibility = View.GONE
binding.usernameTitle.visibility = View.GONE
binding.passwordTitle.visibility = View.GONE
binding.instanceTitle.visibility = View.GONE
binding.username.isEnabled = false
binding.password.isEnabled = false
binding.instance.isEnabled = false
binding.twoFa.setText("")
binding.twoFa.visibility = View.VISIBLE
binding.twoFaTitle.visibility = View.VISIBLE
binding.errorTwoFa.visibility = View.GONE
if (code.equals("invalid_two_factor")) {
binding.error.setText(R.string.invalid_two_fa_token)
binding.error.visibility = View.VISIBLE
}
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = true
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).isEnabled = true
} else {
binding.error.visibility = View.VISIBLE
binding.tryConnect.visibility = View.GONE
binding.tryConnectMsg.visibility = View.GONE
binding.username.visibility = View.VISIBLE
binding.password.visibility = View.VISIBLE
binding.instance.visibility = View.VISIBLE
binding.twoFa.visibility = View.GONE
binding.usernameTitle.visibility = View.VISIBLE
binding.passwordTitle.visibility = View.VISIBLE
binding.instanceTitle.visibility = View.VISIBLE
binding.twoFaTitle.visibility = View.GONE
binding.username.isEnabled = true
binding.password.isEnabled = true
binding.instance.isEnabled = true
binding.twoFa.setText("")
binding.error.text = error
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = true
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).isEnabled = true
}
}
}
@ -158,9 +209,9 @@ class AddInstanceDialog : DialogFragment() {
};
if (this::oAuthData.isInitialized) {
InstanceManager.getUserToken(requireContext(), instance, username, password, oAuthData, listener)
InstanceManager.getUserToken(requireContext(), instance, username, password, twoFaToken, oAuthData, listener)
} else {
InstanceManager.registerAccount(requireContext(), instance, username, password,listener)
InstanceManager.registerAccount(requireContext(), instance, username, password, twoFaToken,listener)
}
}
}

View File

@ -52,21 +52,22 @@ object InstanceManager {
private const val CONFIG_LIVE: String = "live"
private const val CONFIG_LIVE_ENABLED: String = "enabled"
private const val CONFIG_LIVE_SAVE_REPLAY: String = "allowReplay"
public const val INTERNAL_ERROR: String = "INTERNAL_ERROR"
fun registerAccount(context: Context, url: String, username: String, password: String, listener: InstanceListener) {
fun registerAccount(context: Context, url: String, username: String, password: String, twoFa: String, listener: InstanceListener) {
val registerUrl = url + BASE_API_ENDPOINT+ REGISTER_CLIENT_ENDPOINT
val internalListener: InstanceListener = object : InstanceListener {
override fun onSuccess(args: Bundle?) {
val oauthData: OAuthData? = args?.getParcelable(EXTRA_DATA)
oauthData?.baseUrl = url
if (oauthData != null)
getUserToken(context, url, username, password,oauthData, listener)
getUserToken(context, url, username, password, twoFa, oauthData, listener)
else
listener.onError(context.getString(R.string.unknwon_error))
listener.onError(INTERNAL_ERROR,context.getString(R.string.unknwon_error))
}
override fun onError(error: String?) {
listener.onError(error)
override fun onError(code: String?, error: String?) {
listener.onError(code,error)
}
override fun onUpdateOAuthData(oauthData: OAuthData) {
@ -77,9 +78,9 @@ object InstanceManager {
oauthManager.register(context,registerUrl,internalListener)
}
fun getUserToken(context: Context, url: String, username: String, password: String, oauthData: OAuthData, listener: InstanceListener) {
fun getUserToken(context: Context, url: String, username: String, password: String, twoFa: String, oauthData: OAuthData, listener: InstanceListener) {
val userAccess = url + BASE_API_ENDPOINT + GET_USER_CLIENT_ENDPOINT
oauthManager.getUserToken(context, userAccess, username, password, oauthData, listener)
oauthManager.getUserToken(context, userAccess, username, password, twoFa, oauthData, listener)
}
private fun refreshToken(context: Context, url: String, oauthData: OAuthData, listener: InstanceListener) {
@ -91,7 +92,7 @@ object InstanceManager {
if(oauthData.expires < Calendar.getInstance().timeInMillis) {
refreshToken(context,url,oauthData,object: InstanceListener {
override fun onSuccess(args: Bundle?) {
val oauth: OAuthData? = args?.getParcelable(InstanceManager.EXTRA_DATA)
val oauth: OAuthData? = args?.getParcelable(EXTRA_DATA)
if (oauth != null) {
DatabaseManager.updateCredentials(context,oauth)
listener.onUpdateOAuthData(oauth)
@ -99,8 +100,8 @@ object InstanceManager {
}
}
override fun onError(error: String?) {
listener.onError(error)
override fun onError(code: String?, error: String?) {
listener.onError(code,error)
}
override fun onUpdateOAuthData(oauthData: OAuthData) {
@ -119,12 +120,12 @@ object InstanceManager {
override fun onSuccess(args: Bundle?) {
val response = args?.getString(EXTRA_DATA, null)
if (response == null) {
listener.onError(context.getString(R.string.unknwon_error))
listener.onError(INTERNAL_ERROR,context.getString(R.string.unknwon_error))
return
}
val streamData = extractStreamData(response)
if (streamData == null) {
listener.onError(context.getString(R.string.json_error))
listener.onError(INTERNAL_ERROR,context.getString(R.string.json_error))
return
}
val results = Bundle()
@ -132,8 +133,8 @@ object InstanceManager {
listener.onSuccess(results)
}
override fun onError(error: String?) {
listener.onError(error)
override fun onError(code: String?, error: String?) {
listener.onError(code,error)
}
override fun onUpdateOAuthData(oauthData: OAuthData) {
@ -148,43 +149,50 @@ object InstanceManager {
// val comments: Boolean, val download: Boolean, val nsfw: Boolean, val saveReplay: Boolean
val createLiveUrl = url + BASE_API_ENDPOINT + CREATE_LIVE_ENDPOINT
val data = Bundle()
val boundary = "45fcc22"
var formData: String = prepareFormData(boundary,"channelId",streamSettings.channel.toString(),false)
formData += prepareFormData(boundary,"name",streamSettings.title,false)
formData += prepareFormData(boundary,"privacy",streamSettings.privacy.toString(),false)
val formData = JSONObject()
formData.put("channelId",streamSettings.channel)
formData.put("name",streamSettings.title)
formData.put("privacy",streamSettings.privacy)
if(streamSettings.category != null)
formData += prepareFormData(boundary,"category",streamSettings.category.toString(),false)
formData.put("category",streamSettings.category)
if(streamSettings.language != null)
formData += prepareFormData(boundary,"language",streamSettings.language.toString(),false)
formData.put("language",streamSettings.language.toString())
if(streamSettings.description != null)
formData += prepareFormData(boundary,"description",streamSettings.description.toString(),false)
formData.put("description",streamSettings.description)
if(streamSettings.licence != null)
formData += prepareFormData(boundary,"licence",streamSettings.licence.toString(),false)
formData += prepareFormData(boundary,"commentsEnabled",streamSettings.comments.toString(),false)
formData += prepareFormData(boundary,"nsfw",streamSettings.nsfw.toString(),false)
formData += prepareFormData(boundary,"downloadEnabled",streamSettings.download.toString(),streamSettings.saveReplay == null)
if (streamSettings.saveReplay != null)
formData += prepareFormData(boundary,"saveReplay",streamSettings.saveReplay.toString(),true)
data.putString(CONTENT_TYPE,"multipart/form-data; boundary=$boundary")
data.putString(CONTENT_DATA,formData)
formData.put("licence",streamSettings.licence)
formData.put("commentsEnabled",streamSettings.comments.toString())
formData.put("nsfw",streamSettings.nsfw.toString())
formData.put("downloadEnabled",streamSettings.download.toString())
if (streamSettings.saveReplay != null) {
formData.put("saveReplay",streamSettings.saveReplay.toString())
val privacyReplay = JSONObject()
privacyReplay.put("privacy",streamSettings.privacy)
formData.put("replaySettings",privacyReplay)
}
data.putString(CONTENT_TYPE,"application/json")
data.putString(CONTENT_DATA,formData.toString())
println(formData.toString())
val internalListener = object: InstanceListener {
override fun onSuccess(args: Bundle?) {
val response = args?.getString(EXTRA_DATA, null)
if (response == null) {
listener.onError(context.getString(R.string.unknwon_error))
listener.onError(INTERNAL_ERROR,context.getString(R.string.unknwon_error))
return
}
val liveId = extractLiveId(response)
if (liveId == null) {
listener.onError(context.getString(R.string.json_error))
listener.onError(INTERNAL_ERROR,context.getString(R.string.json_error))
return
}
getStreamKey(context,url,oauthData,liveId,listener)
}
override fun onError(error: String?) {
listener.onError(error)
override fun onError(code: String?, error: String?) {
listener.onError(code,error)
}
override fun onUpdateOAuthData(oauthData: OAuthData) {
@ -207,8 +215,8 @@ object InstanceManager {
}
}
override fun onError(error: String?) {
listener.onError(error)
override fun onError(code: String?, error: String?) {
listener.onError(code,error)
}
override fun onUpdateOAuthData(oauthData: OAuthData) {
@ -227,21 +235,21 @@ object InstanceManager {
override fun onSuccess(args: Bundle?) {
val response = args?.getString(EXTRA_DATA, null)
if (response == null) {
listener.onError(context.getString(R.string.unknwon_error))
listener.onError(INTERNAL_ERROR,context.getString(R.string.unknwon_error))
return
}
val channelList = extractChannelData(response)
if (channelList == null) {
listener.onError(context.getString(R.string.json_error))
listener.onError(INTERNAL_ERROR,context.getString(R.string.json_error))
return
}
args.putParcelableArrayList(EXTRA_DATA, channelList)
listener.onSuccess(args)
}
override fun onError(error: String?) {
listener.onError(error)
override fun onError(code: String?, error: String?) {
listener.onError(code,error)
}
override fun onUpdateOAuthData(oauthData: OAuthData) {
@ -258,13 +266,13 @@ object InstanceManager {
override fun onSuccess(args: Bundle?) {
val response = args?.getString(EXTRA_DATA, null)
if (response == null) {
listener.onError(context.getString(R.string.unknwon_error))
listener.onError(INTERNAL_ERROR,context.getString(R.string.unknwon_error))
return
}
val categoryList = extractMapData<Int>(response,0)
if (categoryList == null) {
listener.onError(context.getString(R.string.json_error))
listener.onError(INTERNAL_ERROR,context.getString(R.string.json_error))
return
}
categoryList[""]=0
@ -272,8 +280,8 @@ object InstanceManager {
listener.onSuccess(args)
}
override fun onError(error: String?) {
listener.onError(error)
override fun onError(code: String?, error: String?) {
listener.onError(code,error)
}
override fun onUpdateOAuthData(oauthData: OAuthData) {
@ -291,21 +299,21 @@ object InstanceManager {
override fun onSuccess(args: Bundle?) {
val response = args?.getString(EXTRA_DATA, null)
if (response == null) {
listener.onError(context.getString(R.string.unknwon_error))
listener.onError(INTERNAL_ERROR,context.getString(R.string.unknwon_error))
return
}
val privacyList = extractMapData<Int>(response,0)
if (privacyList == null) {
listener.onError(context.getString(R.string.json_error))
listener.onError(INTERNAL_ERROR,context.getString(R.string.json_error))
return
}
args.putSerializable(EXTRA_DATA, privacyList)
listener.onSuccess(args)
}
override fun onError(error: String?) {
listener.onError(error)
override fun onError(code: String?, error: String?) {
listener.onError(code,error)
}
override fun onUpdateOAuthData(oauthData: OAuthData) {
@ -322,13 +330,13 @@ object InstanceManager {
override fun onSuccess(args: Bundle?) {
val response = args?.getString(EXTRA_DATA, null)
if (response == null) {
listener.onError(context.getString(R.string.unknwon_error))
listener.onError(INTERNAL_ERROR,context.getString(R.string.unknwon_error))
return
}
val licencesList = extractMapData<Int>(response,0)
if (licencesList == null) {
listener.onError(context.getString(R.string.json_error))
listener.onError(INTERNAL_ERROR,context.getString(R.string.json_error))
return
}
licencesList[""]=0
@ -336,8 +344,8 @@ object InstanceManager {
listener.onSuccess(args)
}
override fun onError(error: String?) {
listener.onError(error)
override fun onError(code: String?, error: String?) {
listener.onError(code,error)
}
override fun onUpdateOAuthData(oauthData: OAuthData) {
@ -354,13 +362,13 @@ object InstanceManager {
override fun onSuccess(args: Bundle?) {
val response = args?.getString(EXTRA_DATA, null)
if (response == null) {
listener.onError(context.getString(R.string.unknwon_error))
listener.onError(INTERNAL_ERROR,context.getString(R.string.unknwon_error))
return
}
val languageList = extractMapData<String>(response,"")
if (languageList == null) {
listener.onError(context.getString(R.string.json_error))
listener.onError(INTERNAL_ERROR,context.getString(R.string.json_error))
return
}
languageList[""]=""
@ -368,8 +376,8 @@ object InstanceManager {
listener.onSuccess(args)
}
override fun onError(error: String?) {
listener.onError(error)
override fun onError(code: String?, error: String?) {
listener.onError(code,error)
}
override fun onUpdateOAuthData(oauthData: OAuthData) {
@ -386,20 +394,20 @@ object InstanceManager {
override fun onSuccess(args: Bundle?) {
val response = args?.getString(EXTRA_DATA, null)
if (response == null) {
listener.onError(context.getString(R.string.unknwon_error))
listener.onError(INTERNAL_ERROR,context.getString(R.string.unknwon_error))
return
}
val configData = extractConfigData(response)
if (configData == null) {
listener.onError(context.getString(R.string.json_error))
listener.onError(INTERNAL_ERROR,context.getString(R.string.json_error))
return
}
args.putParcelable(EXTRA_DATA, configData)
listener.onSuccess(args)
}
override fun onError(error: String?) {
listener.onError(error)
override fun onError(code: String?, error: String?) {
listener.onError(code,error)
}
override fun onUpdateOAuthData(oauthData: OAuthData) {
@ -500,6 +508,7 @@ object InstanceManager {
}
}
@Suppress("UNCHECKED_CAST")
private fun <T> extractMapData(response: String, type: T): HashMap<String,T>? {
return try {
val json = JSONObject(response)
@ -517,24 +526,9 @@ object InstanceManager {
}
}
private fun prepareFormData(boundary: String, propertyName: String, property: String, lastData: Boolean): String {
val crlf = "\r\n"
var formData = "--$boundary$crlf"
formData+="Content-Disposition: form-data; name=\"$propertyName\"$crlf$crlf"
formData+="$property$crlf"
if(lastData) {
formData += "--$boundary--$crlf"
}
return formData
}
interface InstanceListener{
fun onSuccess(args: Bundle?)
fun onError(error: String?)
fun onError(code: String?, error: String?)
fun onUpdateOAuthData(oauthData: OAuthData)
}
}

View File

@ -53,12 +53,13 @@ class OAuthManager {
addMessage(message)
}
fun getUserToken(context: Context, url: String, username: String, password: String, oauthData: OAuthData, listener: InstanceManager.InstanceListener) {
fun getUserToken(context: Context, url: String, username: String, password: String, twoFa: String, oauthData: OAuthData, listener: InstanceManager.InstanceListener) {
val args = Bundle()
args.putString(URL, url)
args.putParcelable(OAUTH_DATA, oauthData)
args.putString(USERNAME, username)
args.putString(PASSWORD, password)
args.putString(TWO_FA,twoFa)
val message = Message()
message.type = Message.Message_Type.GET_USER_TOKEN
@ -119,11 +120,13 @@ class OAuthManager {
private const val URL: String = "URL"
private const val USERNAME: String = "USERNAME"
private const val PASSWORD: String = "PASSWORD"
private const val TWO_FA: String = "TWO_FA"
private const val OAUTH_DATA: String = "OAUTH_DATA"
private const val DATA: String = "DATA"
private const val EXTRA_DATA: String = "EXTRA_DATA"
private const val CONTENT_TYPE: String = "CONTENT_TYPE"
private const val CONTENT_DATA: String = "CONTENT_DATA"
fun addMessage(message: Message)
{
@ -153,7 +156,7 @@ class OAuthManager {
fun register(message: Message) {
if (!isConnectedToInternet(message.context)) {
message.listener?.onError(message.context.getString(R.string.network_error))
message.listener?.onError(InstanceManager.INTERNAL_ERROR,message.context.getString(R.string.network_error))
return
}
val url: String = message.args.getString(URL,"")
@ -170,7 +173,7 @@ class OAuthManager {
try {
inputStream = connection.inputStream
} catch (e: UnknownHostException) {
message.listener?.onError(message.context.getString(R.string.unknown_host))
message.listener?.onError(InstanceManager.INTERNAL_ERROR,message.context.getString(R.string.unknown_host))
return
} catch (e : Exception) {
e.printStackTrace()
@ -192,7 +195,7 @@ class OAuthManager {
message.listener?.onSuccess(result)
} else {
message.listener?.onError(message.context.getString(R.string.unknwon_error))
message.listener?.onError(InstanceManager.INTERNAL_ERROR, message.context.getString(R.string.unknwon_error))
}
} else {
handleError(message,response)
@ -201,7 +204,7 @@ class OAuthManager {
fun getUserToken(message: Message) {
if (!isConnectedToInternet(message.context)) {
message.listener?.onError(message.context.getString(R.string.network_error))
message.listener?.onError(InstanceManager.INTERNAL_ERROR,message.context.getString(R.string.network_error))
return
}
val url: String = message.args.getString(URL, "")
@ -220,6 +223,10 @@ class OAuthManager {
}
var username: String = message.args.getString(USERNAME,"")
var password: String = message.args.getString(PASSWORD,"")
var twoFa: String = message.args.getString(TWO_FA,"")
if (twoFa.isNotEmpty()) {
connection.setRequestProperty("x-peertube-otp", twoFa)
}
username = URLEncoder.encode(username,"UTF-8")
password = URLEncoder.encode(password,"UTF-8")
@ -241,7 +248,7 @@ class OAuthManager {
try {
inputStream = connection.inputStream
} catch (e: UnknownHostException) {
message.listener?.onError(message.context.getString(R.string.unknown_host))
message.listener?.onError(InstanceManager.INTERNAL_ERROR,message.context.getString(R.string.unknown_host))
return
} catch (e : Exception) {
e.printStackTrace()
@ -270,7 +277,7 @@ class OAuthManager {
message.listener?.onSuccess(result)
} else {
message.listener?.onError(message.context.getString(R.string.unknwon_error))
message.listener?.onError(InstanceManager.INTERNAL_ERROR,message.context.getString(R.string.unknwon_error))
}
} else {
handleError(message,response)
@ -280,7 +287,7 @@ class OAuthManager {
fun refreshToken(message: Message) {
if (!isConnectedToInternet(message.context)) {
message.listener?.onError(message.context.getString(R.string.network_error))
message.listener?.onError(InstanceManager.INTERNAL_ERROR,message.context.getString(R.string.network_error))
return
}
val url: String = message.args.getString(URL, "")
@ -313,7 +320,7 @@ class OAuthManager {
try {
inputStream = connection.inputStream
} catch (e: UnknownHostException) {
message.listener?.onError(message.context.getString(R.string.unknown_host))
message.listener?.onError(InstanceManager.INTERNAL_ERROR,message.context.getString(R.string.unknown_host))
return
} catch (e : Exception) {
e.printStackTrace()
@ -341,7 +348,7 @@ class OAuthManager {
message.listener?.onSuccess(result)
} else {
message.listener?.onError(message.context.getString(R.string.unknwon_error))
message.listener?.onError(InstanceManager.INTERNAL_ERROR, message.context.getString(R.string.unknwon_error))
}
} else {
handleError(message,response)
@ -351,7 +358,7 @@ class OAuthManager {
fun post(message: Message) {
if (!isConnectedToInternet(message.context)) {
message.listener?.onError(message.context.getString(R.string.network_error))
message.listener?.onError(InstanceManager.INTERNAL_ERROR,message.context.getString(R.string.network_error))
return
}
val url: String = message.args.getString(URL, "")
@ -381,7 +388,7 @@ class OAuthManager {
outputStream.flush()
outputStream.close()
} catch (e: UnknownHostException) {
message.listener?.onError(message.context.getString(R.string.unknown_host))
message.listener?.onError(InstanceManager.INTERNAL_ERROR,message.context.getString(R.string.unknown_host))
return
} catch (e : Exception) {
e.printStackTrace()
@ -394,7 +401,7 @@ class OAuthManager {
try {
inputStream = connection.inputStream
} catch (e: UnknownHostException) {
message.listener?.onError(message.context.getString(R.string.unknown_host))
message.listener?.onError(InstanceManager.INTERNAL_ERROR,message.context.getString(R.string.unknown_host))
return
} catch (e: Exception) {
e.printStackTrace()
@ -410,7 +417,7 @@ class OAuthManager {
result.putString(EXTRA_DATA, response)
message.listener?.onSuccess(result)
} else {
message.listener?.onError(message.context.getString(R.string.unknwon_error))
message.listener?.onError(InstanceManager.INTERNAL_ERROR,message.context.getString(R.string.unknwon_error))
}
} else {
handleError(message,response)
@ -420,7 +427,7 @@ class OAuthManager {
fun get(message: Message) {
if (!isConnectedToInternet(message.context)) {
message.listener?.onError(message.context.getString(R.string.network_error))
message.listener?.onError(InstanceManager.INTERNAL_ERROR,message.context.getString(R.string.network_error))
return
}
val url: String = message.args.getString(URL, "")
@ -443,7 +450,7 @@ class OAuthManager {
try {
inputStream = connection.inputStream
} catch (e: UnknownHostException) {
message.listener?.onError(message.context.getString(R.string.unknown_host))
message.listener?.onError(InstanceManager.INTERNAL_ERROR,message.context.getString(R.string.unknown_host))
return
} catch (e : Exception) {
e.printStackTrace()
@ -458,7 +465,7 @@ class OAuthManager {
result.putString(EXTRA_DATA, response)
message.listener?.onSuccess(result)
} else {
message.listener?.onError(message.context.getString(R.string.unknwon_error))
message.listener?.onError(InstanceManager.INTERNAL_ERROR,message.context.getString(R.string.unknwon_error))
}
} else {
handleError(message,response)
@ -470,12 +477,13 @@ class OAuthManager {
try {
val rootObj = JSONObject(response)
val error = rootObj.getString("error")
message.listener?.onError(error)
val code = rootObj.getString("code")
message.listener?.onError(code,error)
} catch (e: Exception) {
message.listener?.onError(message.context.getString(R.string.json_error))
message.listener?.onError(InstanceManager.INTERNAL_ERROR,message.context.getString(R.string.json_error))
}
} else {
message.listener?.onError(message.context.getString(R.string.unknwon_error))
message.listener?.onError(InstanceManager.INTERNAL_ERROR,message.context.getString(R.string.unknwon_error))
}
}

View File

@ -52,7 +52,7 @@ class StreamSettings(
parcel.writeByte(if (nsfw) 1 else 0)
if (saveReplay!= null)
parcel.writeByte(if (saveReplay) 1 else 0)
parcel.writeInt(resolution?.ordinal!!)
parcel.writeInt(resolution.ordinal)
}
override fun describeContents(): Int {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 1.7 MiB

View File

@ -75,6 +75,23 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/password_error"/>
<TextView
android:id="@+id/two_fa_title"
android:text="@string/two_fa"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<EditText
android:id="@+id/two_fa"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="number"/>
<TextView
android:id="@+id/error_two_fa"
android:textColor="#FF0000"
android:layout_gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/two_fa_error"/>
</LinearLayout>
</layout>

View File

@ -8,7 +8,6 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000"
android:keepScreenOn="true"
>
<TextView
@ -16,7 +15,6 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="@+id/permission_info"
android:textColor="@color/white"
android:text="@string/permissions"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
@ -41,7 +39,7 @@
android:layout_height="0dp"
android:id="@+id/surfaceView"
app:keepAspectRatio="true"
app:aspectRatioMode="adjust_rotate"
app:aspectRatioMode="adjust"
app:AAEnabled="false"
app:numFilters="1"
app:isFlipHorizontal="false"
@ -60,8 +58,7 @@
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toStartOf="@id/flash"
app:layout_constraintStart_toStartOf="parent"
app:srcCompat="@drawable/baseline_screen_rotation_24"
app:tint="@color/white" />
app:srcCompat="@drawable/baseline_screen_rotation_24" />
<ImageView
android:layout_marginTop="15dp"
android:id="@+id/flash"
@ -70,8 +67,7 @@
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toStartOf="@id/mute_micro"
app:layout_constraintStart_toEndOf="@id/rotation"
app:srcCompat="@drawable/baseline_flash_off_24"
app:tint="@color/white" />
app:srcCompat="@drawable/baseline_flash_off_24" />
<ImageView
android:layout_marginTop="15dp"
android:id="@+id/mute_micro"
@ -80,8 +76,7 @@
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@id/flash"
app:layout_constraintEnd_toStartOf="@id/switch_camera"
app:srcCompat="@drawable/baseline_volume_up_24"
app:tint="@color/white" />
app:srcCompat="@drawable/baseline_volume_up_24"/>
<ImageView
android:layout_marginTop="15dp"
android:id="@+id/switch_camera"
@ -90,8 +85,7 @@
app:srcCompat="@drawable/baseline_cameraswitch_24"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@id/mute_micro"
app:layout_constraintEnd_toEndOf="parent"
app:tint="@color/white" />
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/stop"

View File

@ -0,0 +1,281 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="stream_title_error">Název by neměl být prázdný</string>
<string name="instance_error">Instance nemůže být prázdná</string>
<string name="username_error">Uživatelské jméno nesmí být prázdné</string>
<string name="password_error">Heslo nesmí být prázdné</string>
<string name="account_exist">Tento účet již existuje</string>
<string name="loading_channels">Načítání seznamu kanálů</string>
<string name="try_connect">Čekání na připojení</string>
<string name="delete_account">Odstranit účet %s spojený se serverem %s\?</string>
<string name="tags_rules">Maximálně 5 značek, každá od 2 do 30 znaků, oddělených čárkou</string>
<string name="network_error">Nenalezeno připojení k internetu</string>
<string name="app_name">PeerTube Live</string>
<string name="unknwon_error">Neznámá chyba</string>
<string name="unknown_host">Neznámý hostitel</string>
<string name="json_error">JSON chyba</string>
<string name="malformed_instance_error">Instance musí mít platnou adresu URL</string>
<string name="no_instance">Není registrován žádný účet. Přidejte účet PeerTube pomocí tlačítka \"+\" v horní liště</string>
<string name="permissions">Pro živé vysílání je nutný přístup ke kameře a mikrofonu. Kliknutím níže změníte nastavení oprávnění.</string>
<string name="back_reason">Váš živý přenos skončil po stisknutí tlačítka zpět</string>
<string name="background_reason">Váš živý přenos skončil, protože aplikace přešla na pozadí</string>
<string name="save_replay_info">Pokud tuto možnost povolíte, bude váš živý přenos ukončen, pokud překročíte kvótu videí</string>
<string name="password">Heslo</string>
<string name="delete_account_title">Smazat tento účet</string>
<string name="download_enabled">Povolit stahování</string>
<string name="nsfw">Obsahuje citlivý obsah</string>
<string name="tags">Štítky</string>
<string name="description">Popis</string>
<string name="creating">Čekání na živou tvorbu</string>
<string name="stream_resolution">Rozlišení živého přenosu</string>
<string name="entertainment">Zábava</string>
<string name="news_politics">Zprávy a Politika</string>
<string name="how_to">Jak na to</string>
<string name="education">Vzdělání</string>
<string name="activism">Aktivismus</string>
<string name="science_tech">Věda a Technika</string>
<string name="animals">Zvířata</string>
<string name="kids">Děti</string>
<string name="food">Jídlo</string>
<string name="afar">Afarština</string>
<string name="abkhazian">Abcházština</string>
<string name="afrikaans">Afrikánština</string>
<string name="akan">Akanština</string>
<string name="amharic">Amharština</string>
<string name="aragonese">Aragonština</string>
<string name="american_sign_language">Americký znakový jazyk</string>
<string name="assamese">Assámština</string>
<string name="bashkir">Baškirština</string>
<string name="bambara">Bambara</string>
<string name="belarusian">Běloruština</string>
<string name="british_sign_language">Britský znakový jazyk</string>
<string name="bislama">Bislama</string>
<string name="tibetan">Tibetština</string>
<string name="bosnian">Bosenský jazyk</string>
<string name="breton">Bretonština</string>
<string name="brazilian_sign_language">Brazilský znakový jazyk</string>
<string name="catalan">Katalánština</string>
<string name="czech">Čeština</string>
<string name="chamorro">Chamorro</string>
<string name="chechen">Čečenština</string>
<string name="chuvash">Čuvašština</string>
<string name="cornish">Cornwallština</string>
<string name="cree">Cree</string>
<string name="czech_sign_language">Český znakový jazyk</string>
<string name="welsh">Velština</string>
<string name="danish">Dánština</string>
<string name="german">Němčina</string>
<string name="dhivehi">Dhivehi</string>
<string name="danish_sign_language">Dánský znakový jazyk</string>
<string name="dzongkha">Džongkha</string>
<string name="greek">Řečtina</string>
<string name="english">Angličtina</string>
<string name="esperanto">Esperanto</string>
<string name="faroese">Faerština</string>
<string name="persian">Perština</string>
<string name="french">Francouzština</string>
<string name="western_frisian">Západofríština</string>
<string name="galician">Galicijština</string>
<string name="manx">Manx</string>
<string name="guarani">Guarani</string>
<string name="german_sign_language">Německý znakový jazyk</string>
<string name="gujarati">Gudžarátština</string>
<string name="hausa">Hausa</string>
<string name="hebrew">Hebrejština</string>
<string name="herero">Herero</string>
<string name="hindi">Hindština</string>
<string name="hiri_motu">Hiri Motu</string>
<string name="croatian">Chorvatština</string>
<string name="hungarian">Maďarština</string>
<string name="armenian">Arménština</string>
<string name="igbo">Igbo</string>
<string name="sichuan_yi">Sečuánský jazyk Yi</string>
<string name="inuktitut">Inuktitut</string>
<string name="indonesian">Indonéština</string>
<string name="inupiaq">Inupiaq</string>
<string name="icelandic">Islandština</string>
<string name="italian">Italština</string>
<string name="javanese">Javánština</string>
<string name="lojban">Lojban</string>
<string name="japanese_sign_language">Japonský znakový jazyk</string>
<string name="kannada">Kannadština</string>
<string name="georgian">Gruzínština</string>
<string name="kanuri">Kanuri</string>
<string name="kazakh">Kazaština</string>
<string name="khmer">Khmerština</string>
<string name="kikuyu">Kikuyu</string>
<string name="kirghiz">Kirgizština</string>
<string name="komi">Komi</string>
<string name="kongo">Kongo</string>
<string name="korean">Korejština</string>
<string name="kurdish">Kurdština</string>
<string name="latvian">Lotyšština</string>
<string name="limburgan">Limburština</string>
<string name="luxembourgish">Lucemburština</string>
<string name="ganda">Ganda</string>
<string name="marshallese">Maršálština</string>
<string name="malayalam">Malajalamština</string>
<string name="marathi">Maráthština</string>
<string name="macedonian">Makedonština</string>
<string name="malagasy">Malgaština</string>
<string name="maltese">Maltézština</string>
<string name="mongolian">Mongolština</string>
<string name="maori">Maorština</string>
<string name="malay_macrolanguage">Malajština (makrojazyk)</string>
<string name="navajo">Navajština</string>
<string name="south_ndebele">Jižní Ndebele</string>
<string name="ndonga">Ndonga</string>
<string name="nepali_macrolanguage">Nepálština (makrojazyk)</string>
<string name="dutch">Nizozemština</string>
<string name="norwegian_nynorsk">Norština Nynorsk</string>
<string name="norwegian_bokmål">Norština Bokmål</string>
<string name="norwegian">Norština</string>
<string name="nyanja">Nyanja</string>
<string name="ojibwa">Jazyk Odžibvejů</string>
<string name="oriya_macrolanguage">Oriya (makrojazyk)</string>
<string name="ossetian">Osetijština</string>
<string name="panjabi">Pandžábština</string>
<string name="polish">Polština</string>
<string name="portuguese">Portugalština</string>
<string name="pushto">Paštúnština</string>
<string name="quechua">Kečuánština</string>
<string name="romanian">Rumunština</string>
<string name="romansh">Romština</string>
<string name="russian_sign_language">Ruský znakový jazyk</string>
<string name="south_african_sign_language">Jihoafrický znakový jazyk</string>
<string name="sinhala">Sinhálština</string>
<string name="slovak">Slovenština</string>
<string name="southern_sotho">Jižní Sotho</string>
<string name="albanian">Albánština</string>
<string name="tahitian">Tahitština</string>
<string name="tamil">Tamilština</string>
<string name="telugu">Telugu</string>
<string name="tajik">Tádžijština</string>
<string name="tagalog">Tagalog</string>
<string name="tigrinya">Tigrinya</string>
<string name="tsonga">Tsonga</string>
<string name="bysa">Uveďte autora - Sdílejte stejně</string>
<string name="byncsa">Uveďte autora - nekomerční - sdílejte stejně</string>
<string name="privacy_private">Soukromé</string>
<string name="internal">Interní</string>
<string name="live_disabled">Tato instance má vypnutý livestream.</string>
<string name="stream_privacy">Soukromí</string>
<string name="yes">Ano</string>
<string name="add_instance">Přidat tento účet</string>
<string name="aymara">Aymarština</string>
<string name="gaming">Hry</string>
<string name="fulah">Fulah</string>
<string name="lock_reason">Váš živý přenos skončil, protože telefon byl uzamčen</string>
<string name="ask_end_stream">Chcete zastavit živý přenos\?</string>
<string name="stream_category">Kategorie</string>
<string name="stream_language">Jazyk</string>
<string name="go_live">Spustit živý přenos</string>
<string name="stream_licence">Licence</string>
<string name="username">Uživatelské jméno</string>
<string name="goto_permissions">Zobrazit nastavení</string>
<string name="stream_title">Název</string>
<string name="stop_reason">Váš živý přenos skončil</string>
<string name="network_reason">Váš živý přenos skončil kvůli problému se sítí</string>
<string name="choose_channel">Kanál</string>
<string name="advanced_settings">▶ Pokročilá nastavení</string>
<string name="advanced_settings_expand">▼ Pokročilá nastavení</string>
<string name="connection">Připojení</string>
<string name="comments_enabled">Povolit komentáře k videu</string>
<string name="films">Filmy</string>
<string name="azerbaijani">Ázerbájdžán</string>
<string name="cancel">Zrušit</string>
<string name="connect">Připojit</string>
<string name="vehicles">Auta</string>
<string name="no">Ne</string>
<string name="chinese_sign_language">Čínský znakový jazyk</string>
<string name="stream_ended">Živý přenos skončil</string>
<string name="exemple_instance">např. peertube.fr, https://peertube.fr</string>
<string name="music">Hudba</string>
<string name="travels">Cestování</string>
<string name="arabic">Arabština</string>
<string name="avaric">Avaric</string>
<string name="instance">Instance</string>
<string name="save_replay">Automatické zveřejnění přehrávání po skončení živého vysílání</string>
<string name="end_stream">Zastavit živý přenos</string>
<string name="comedy">Komedie</string>
<string name="art">Umění</string>
<string name="sports">Sporty</string>
<string name="people">Lidé</string>
<string name="corsican">Korsičtina</string>
<string name="kotava">Kotava</string>
<string name="bengali">Bengálština</string>
<string name="bulgarian">Bulharština</string>
<string name="estonian">Estonština</string>
<string name="fijian">Fidžijština</string>
<string name="french_sign_language">Francouzský znakový jazyk</string>
<string name="scottish_gaelic">Skotská gaelština</string>
<string name="ewe">Ewe</string>
<string name="haitian">Haitský jazyk</string>
<string name="serbo_croatian">Srbochorvatština</string>
<string name="basque">Baskičtina</string>
<string name="finnish">Finština</string>
<string name="irish">Irština</string>
<string name="japanese">Japonština</string>
<string name="kabyle">Kabyle</string>
<string name="kalaallisut">Kalaallisut</string>
<string name="kashmiri">Kašmírština</string>
<string name="kinyarwanda">Kinyarwanda</string>
<string name="kuanyama">Kuanyama</string>
<string name="lao">Lao</string>
<string name="sindhi">Sindhi</string>
<string name="burmese">Barmština</string>
<string name="occitan">Okcitánština</string>
<string name="somali">Somálština</string>
<string name="luba_katanga">Luba-Katanga</string>
<string name="nauru">Nauru</string>
<string name="north_ndebele">Severní Ndebele</string>
<string name="lingala">Lingala</string>
<string name="lithuanian">Litevština</string>
<string name="pakistan_sign_language">Pákistánský znakový jazyk</string>
<string name="oromo">Oromo</string>
<string name="russian">Ruština</string>
<string name="saudi_arabian_sign_language">Saúdskoarabský znakový jazyk</string>
<string name="samoan">Samojský jazyk</string>
<string name="serbian">Srbština</string>
<string name="swedish_sign_language">Švédský znakový jazyk</string>
<string name="thai">Thajština</string>
<string name="turkmen">Turkmenština</string>
<string name="twi">Twi</string>
<string name="urdu">Urdština</string>
<string name="uzbek">Uzbečtina</string>
<string name="walloon">Waloonština</string>
<string name="tatar">Tatarština</string>
<string name="turkish">Turečtina</string>
<string name="uighur">Ujgurština</string>
<string name="yiddish">Jiddiš</string>
<string name="sardinian">Sardinijština</string>
<string name="rundi">Rundi</string>
<string name="sango">Sango</string>
<string name="slovenian">Slovinština</string>
<string name="shona">Shona</string>
<string name="spanish">Španělština</string>
<string name="klingon">Klingonština</string>
<string name="vietnamese">Vietnamština</string>
<string name="wolof">Wolof</string>
<string name="xhosa">Xhosa</string>
<string name="zhuang">Čuang</string>
<string name="northern_sami">Severosamijský jazyk</string>
<string name="swati">Swati</string>
<string name="sundanese">Sundánština</string>
<string name="swahili_macrolanguage">Svahilština (makrojazyk)</string>
<string name="swedish">Švédština</string>
<string name="tonga_tonga_islands">Tonga (Ostrovy Tonga)</string>
<string name="tswana">Tswana</string>
<string name="ukrainian">Ukrajinština</string>
<string name="venda">Venda</string>
<string name="yoruba">Jorubština</string>
<string name="chinese">Čínština</string>
<string name="zulu">Zulujština</string>
<string name="by">Přiznání autorství</string>
<string name="bynd">Uveďte autora - bez odvozenin</string>
<string name="privacy_public">Veřejné</string>
<string name="unlisted">Nezaneseno do seznamu</string>
<string name="bync">Uveďte autora - Nekomerční</string>
<string name="byncnd">Uveďte autora - nekomerční - bez odvozenin</string>
<string name="public_domain">Vyhrazení veřejné domény</string>
</resources>

View File

@ -0,0 +1,281 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="no_instance">Kein Konto registriert. Um ein Peertube-Konto hinzuzufügen, klicke auf das + in der oberen Leiste</string>
<string name="german">Deutsch</string>
<string name="arabic">Arabisch</string>
<string name="food">Essen</string>
<string name="kids">Kinder</string>
<string name="animals">Tiere</string>
<string name="science_tech">Wissenschaft und Technik</string>
<string name="activism">Aktivismus</string>
<string name="education">Bildung</string>
<string name="how_to">Tutorials</string>
<string name="news_politics">Nachrichten und Politik</string>
<string name="entertainment">Unterhaltung</string>
<string name="comedy">Humor</string>
<string name="people">Menschen</string>
<string name="gaming">Videospiele</string>
<string name="travels">Reise</string>
<string name="sports">Sport</string>
<string name="art">Kunst</string>
<string name="vehicles">Fahrzeuge</string>
<string name="films">Filme</string>
<string name="music">Musik</string>
<string name="description">Beschreibung</string>
<string name="tags">Markierungen</string>
<string name="nsfw">Enthält sensible Inhalte</string>
<string name="download_enabled">Herunterladen-Option aktivieren</string>
<string name="comments_enabled">Videokommentare aktivieren</string>
<string name="delete_account_title">Dieses Konto löschen</string>
<string name="instance">Instanz</string>
<string name="password">Passwort</string>
<string name="username">Benutzername</string>
<string name="add_instance">Dieses Konto hinzufügen</string>
<string name="advanced_settings_expand">▼ Erweiterte Einstellungen</string>
<string name="advanced_settings">▶ Erweiterte Einstellungen</string>
<string name="stream_licence">Lizenz</string>
<string name="stream_language">Sprache</string>
<string name="stream_privacy">Privatsphäre</string>
<string name="stream_category">Kategorie</string>
<string name="stream_title">Titel</string>
<string name="connect">Verbinden</string>
<string name="goto_permissions">Einstellungen anzeigen</string>
<string name="no">Nein</string>
<string name="yes">Ja</string>
<string name="cancel">Abbrechen</string>
<string name="choose_channel">Kanal</string>
<string name="stop_reason">Dein Livestream ist beendet</string>
<string name="background_reason">Dein Livestream ist beendet, weil die Anwendung in den Hintergrund gegangen ist</string>
<string name="back_reason">Dein Livestream wurde beendet, nachdem Du die Zurück-Taste gedrückt hast</string>
<string name="tags_rules">Maximal 5 Markierungen, jeweils zwischen 2 und 30 Zeichen, durch Komma getrennt</string>
<string name="delete_account">Bist du sicher, dass du das Konto %s, das mit dem Server %s verbunden ist, löschen möchtest\?</string>
<string name="permissions">Wir benötigen den Zugriff auf Kamera und Mikrofon, um live zu streamen. Zum Ändern der Berechtigungseinstellungen klicke bitte unten.</string>
<string name="try_connect">Warte auf Verbindung</string>
<string name="loading_channels">Deine Kanalliste wird geladen</string>
<string name="account_exist">Dieses Konto existiert bereits</string>
<string name="malformed_instance_error">Instanz sollte eine gültige URL sein</string>
<string name="password_error">Passwort kann nicht leer sein</string>
<string name="username_error">Benutzername kann nicht leer sein</string>
<string name="instance_error">Instanz kann nicht leer sein</string>
<string name="stream_title_error">Titel sollte nicht leer sein</string>
<string name="json_error">JSON-Fehler</string>
<string name="unknown_host">Unbekannter Host</string>
<string name="unknwon_error">Unbekannter Fehler</string>
<string name="network_error">Keine Internetverbindung gefunden</string>
<string name="app_name">Peertube Live</string>
<string name="bashkir">Baschkirisch</string>
<string name="british_sign_language">Britische Gebärdensprache</string>
<string name="bislama">Bislama</string>
<string name="tibetan">Tibetisch</string>
<string name="bosnian">Bosnisch</string>
<string name="breton">Bretonisch</string>
<string name="bulgarian">Bulgarisch</string>
<string name="brazilian_sign_language">Brasilianische Gebärdensprache</string>
<string name="chechen">Tschetschenisch</string>
<string name="chuvash">Tschuwaschisch</string>
<string name="danish">Dänisch</string>
<string name="dzongkha">Dzongkha</string>
<string name="greek">Griechisch</string>
<string name="estonian">Estnisch</string>
<string name="basque">Baskisch</string>
<string name="persian">Persisch</string>
<string name="fijian">Fidschianisch</string>
<string name="finnish">Finnisch</string>
<string name="french">Französisch</string>
<string name="western_frisian">Westfriesisch</string>
<string name="japanese_sign_language">Japanische Gebärdensprache</string>
<string name="kirghiz">Kirgisisch</string>
<string name="norwegian_nynorsk">Norwegisch (Nynorsk)</string>
<string name="norwegian_bokmål">Norwegisch (Bokmål)</string>
<string name="polish">Polnisch</string>
<string name="portuguese">Portugiesisch</string>
<string name="pushto">Paschtu</string>
<string name="quechua">Quechua</string>
<string name="spanish">Spanisch</string>
<string name="albanian">Albanisch</string>
<string name="tamil">Tamilisch</string>
<string name="uzbek">Usbekisch</string>
<string name="vietnamese">Vietnamesisch</string>
<string name="walloon">Wallonisch</string>
<string name="zhuang">Zhuang</string>
<string name="bync">Namensnennung - nicht kommerziell</string>
<string name="bysa">Namensnennung - Weitergabe unter gleichen Bedingungen</string>
<string name="byncnd">Namensnennung - Nicht kommerziell - Keine Derivate</string>
<string name="public_domain">Gemeingut Widmung</string>
<string name="byncsa">Namensnennung - Nicht kommerziell - Weitergabe unter gleichen Bedingungen</string>
<string name="privacy_public">Öffentlich</string>
<string name="unlisted">Ungelistet</string>
<string name="privacy_private">Privat</string>
<string name="internal">Intern</string>
<string name="yiddish">Jiddisch</string>
<string name="cornish">Kornisch</string>
<string name="corsican">Korsisch</string>
<string name="welsh">Walisisch</string>
<string name="latvian">Lettisch</string>
<string name="cree">Cree</string>
<string name="faroese">Färöisch</string>
<string name="english">Englisch</string>
<string name="esperanto">Esperanto</string>
<string name="ewe">Ewe</string>
<string name="scottish_gaelic">Schottisch-Gälisch</string>
<string name="irish">Irisch</string>
<string name="gujarati">Gujarati</string>
<string name="haitian">Haitianisch</string>
<string name="serbo_croatian">Serbo-kroatisch</string>
<string name="hungarian">Ungarisch</string>
<string name="armenian">Armenisch</string>
<string name="icelandic">Isländisch</string>
<string name="javanese">Javanisch</string>
<string name="kabyle">Kabylisch</string>
<string name="georgian">Georgisch</string>
<string name="korean">Koreanisch</string>
<string name="norwegian">Norwegisch</string>
<string name="panjabi">Panjabi</string>
<string name="romansh">Rätoromanisch</string>
<string name="romanian">Rumänisch</string>
<string name="russian">Russisch</string>
<string name="sinhala">Singhalesisch</string>
<string name="slovak">Slowakisch</string>
<string name="northern_sami">Nordsamisch</string>
<string name="serbian">Serbisch</string>
<string name="tigrinya">Tigrinya</string>
<string name="turkish">Türkisch</string>
<string name="uighur">Uigurisch</string>
<string name="ukrainian">Ukrainisch</string>
<string name="save_replay_info">Wenn Sie diese Option aktivieren, wird Ihre Live-Sendung beendet, wenn Sie Ihr Videokontingent überschreiten</string>
<string name="lock_reason">Dein Livestream wurde beendet, weil das Telefon gesperrt wurde</string>
<string name="zulu">Zulu</string>
<string name="yoruba">Yoruba</string>
<string name="bengali">Bengalisch</string>
<string name="german_sign_language">Deutsche Gebärdensprache</string>
<string name="japanese">Japanisch</string>
<string name="lithuanian">Litauisch</string>
<string name="luxembourgish">Luxemburgisch</string>
<string name="dutch">Niederländisch</string>
<string name="by">Namensnennung</string>
<string name="bynd">Namensnennung - Keine Derivate</string>
<string name="chinese">Chinesisch</string>
<string name="american_sign_language">Amerikanische Gebärdensprache</string>
<string name="azerbaijani">Aserbaidschanisch</string>
<string name="bambara">Bambara</string>
<string name="belarusian">Weißrussisch</string>
<string name="czech_sign_language">Tschechische Gebärdensprache</string>
<string name="chinese_sign_language">Chinesische Gebärdensprache</string>
<string name="french_sign_language">Französische Gebärdensprache</string>
<string name="hebrew">Hebräisch</string>
<string name="croatian">Kroatisch</string>
<string name="italian">Italienisch</string>
<string name="lojban">Lojban</string>
<string name="pakistan_sign_language">Pakistanische Gebärdensprache</string>
<string name="tahitian">Tahitianisch</string>
<string name="tajik">Tadschikisch</string>
<string name="galician">Galizisch</string>
<string name="manx">Manx</string>
<string name="hausa">Hausa</string>
<string name="slovenian">Slowenisch</string>
<string name="samoan">Samoanisch</string>
<string name="swedish">Schwedisch</string>
<string name="russian_sign_language">Russische Gebärdensprache</string>
<string name="swedish_sign_language">Schwedische Gebärdensprache</string>
<string name="urdu">Urdu</string>
<string name="connection">Verbindung</string>
<string name="stream_ended">Livestream beendet</string>
<string name="creating">Warte auf die Erstellung des Livestreams</string>
<string name="stream_resolution">Auflösung des Livestreams</string>
<string name="exemple_instance">z.B. peertube.fr, https://peertube.fr</string>
<string name="save_replay">Livestream automatisch veröffentlichen sobald dieser beendet wurde</string>
<string name="end_stream">Livestream beenden</string>
<string name="abkhazian">Abchasisch</string>
<string name="afrikaans">Afrikaans</string>
<string name="afar">Afar</string>
<string name="amharic">Amharisch</string>
<string name="aragonese">Aragonesisch</string>
<string name="akan">Akan</string>
<string name="assamese">Assamesisch</string>
<string name="avaric">Awarisch</string>
<string name="kotava">Kotava</string>
<string name="aymara">Aymara</string>
<string name="catalan">Katalanisch</string>
<string name="czech">Tschechisch</string>
<string name="chamorro">Chamorro</string>
<string name="dhivehi">Dhivehi</string>
<string name="danish_sign_language">Dänische Zeichensprache</string>
<string name="fulah">Fulfulde</string>
<string name="guarani">Guaraní</string>
<string name="herero">Herero</string>
<string name="hindi">Hindi</string>
<string name="hiri_motu">Hiri Motu</string>
<string name="igbo">Igbo</string>
<string name="sichuan_yi">Sichuan Yi</string>
<string name="inuktitut">Inuktitut</string>
<string name="indonesian">Indonesisch</string>
<string name="inupiaq">Inupiaq</string>
<string name="kannada">Kannada</string>
<string name="kashmiri">Kaschmiri</string>
<string name="kazakh">Kasachisch</string>
<string name="kalaallisut">Kalaallisut</string>
<string name="kanuri">Kanuri</string>
<string name="khmer">Khmer</string>
<string name="kikuyu">Kikuyu</string>
<string name="kinyarwanda">Ruandisch</string>
<string name="kongo">Kongo</string>
<string name="komi">Komi</string>
<string name="kuanyama">Kwanyama</string>
<string name="kurdish">Kurdisch</string>
<string name="lao">Lao</string>
<string name="limburgan">Limburgisch</string>
<string name="lingala">Lingala</string>
<string name="luba_katanga">Luba-Katanga</string>
<string name="ganda">Ganda</string>
<string name="marshallese">Marshallesisch</string>
<string name="malayalam">Malayalam</string>
<string name="marathi">Marathi</string>
<string name="macedonian">Mazedonisch</string>
<string name="maltese">Maltesisch</string>
<string name="mongolian">Mongolisch</string>
<string name="malagasy">Madegassisch</string>
<string name="maori">Maori</string>
<string name="malay_macrolanguage">Malaiisch (Makrosprache)</string>
<string name="burmese">Birmesisch</string>
<string name="nauru">Nauruisch</string>
<string name="navajo">Navaho</string>
<string name="south_ndebele">Süd-Ndebele</string>
<string name="north_ndebele">Nord-Ndebele</string>
<string name="ndonga">Ndonga</string>
<string name="nepali_macrolanguage">Nepali (Makrosprache)</string>
<string name="occitan">Okzitanisch</string>
<string name="nyanja">Nyanja</string>
<string name="oromo">Oromo</string>
<string name="ossetian">Ossetisch</string>
<string name="oriya_macrolanguage">Odia (Makrosprache)</string>
<string name="ojibwa">Ojibwe</string>
<string name="saudi_arabian_sign_language">Saudi-Arabische Gebärdensprache</string>
<string name="south_african_sign_language">Südafrikanische Gebärdensprache</string>
<string name="sango">Sango</string>
<string name="rundi">Kirundi</string>
<string name="somali">Somali</string>
<string name="southern_sotho">Sesotho</string>
<string name="sindhi">Sindhi</string>
<string name="shona">Shona</string>
<string name="sardinian">Sardisch</string>
<string name="sundanese">Sundanesisch</string>
<string name="swati">Swati</string>
<string name="swahili_macrolanguage">Swahili (Makrosprache)</string>
<string name="tatar">Tatarisch</string>
<string name="telugu">Telugu</string>
<string name="tagalog">Tagalog</string>
<string name="klingon">Klingonisch</string>
<string name="thai">Thai</string>
<string name="turkmen">Turkmenisch</string>
<string name="tsonga">Tsonga</string>
<string name="tswana">Tswana</string>
<string name="tonga_tonga_islands">Tonga (Tonga-Inseln)</string>
<string name="twi">Twi</string>
<string name="wolof">Wolof</string>
<string name="xhosa">Xhosa</string>
<string name="venda">Venda</string>
<string name="ask_end_stream">Willst du den Livestream beenden\?</string>
<string name="live_disabled">Auf dieser Instanz sind Live-Streams deaktiviert</string>
<string name="go_live">Starte Livestream</string>
<string name="network_reason">Ihr Livestream wurde beendet, aufgrund eines Netzwerkproblems</string>
</resources>

View File

@ -0,0 +1,281 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="json_error">Error JSON</string>
<string name="username_error">El nombre de usuario no puede estar vacío</string>
<string name="account_exist">Esta cuenta ya existe</string>
<string name="no_instance">No hay cuenta registrada. Agregue una cuenta de PeerTube usando el \'+\' en la barra superior</string>
<string name="loading_channels">Cargando tu lista de canales</string>
<string name="try_connect">Esperando la conexión</string>
<string name="save_replay_info">Si habilita esta opción, su transmisión en vivo se cancelará si excede su cuota de video</string>
<string name="back_reason">Tu transmisión en vivo terminó después de presionar el botón Atrás</string>
<string name="stop_reason">Tu transmisión en vivo terminó</string>
<string name="ask_end_stream">¿Quieres detenerla detener la transmisión en vivo\?</string>
<string name="network_reason">Tu transmisión en vivo terminó debido a un problema de red</string>
<string name="choose_channel">Canal</string>
<string name="go_live">Iniciar transmisión en vivo</string>
<string name="yes"></string>
<string name="goto_permissions">Ver configuraciones</string>
<string name="connect">Conectar</string>
<string name="stream_category">Categoría</string>
<string name="stream_privacy">Privacidad</string>
<string name="stream_language">Idioma</string>
<string name="advanced_settings_expand">▼ Ajustes avanzados</string>
<string name="add_instance">Agregar esta cuenta</string>
<string name="username">Nombre de usuario</string>
<string name="instance">Instancia</string>
<string name="delete_account_title">Eliminar esta cuenta</string>
<string name="nsfw">Contiene contenido sensible</string>
<string name="tags">Etiquetas</string>
<string name="description">Descripción</string>
<string name="end_stream">Detener transmisión en vivo</string>
<string name="exemple_instance">e.g. peertube.fr, https://peertube.fr</string>
<string name="music">Música</string>
<string name="films">Peliculas</string>
<string name="art">Arte</string>
<string name="how_to">Cómo hacer</string>
<string name="education">Educación</string>
<string name="science_tech">Ciencias y Tecnología</string>
<string name="food">Comida</string>
<string name="afar">Lejos</string>
<string name="akan">Voluntad</string>
<string name="amharic">Amárico</string>
<string name="arabic">Arábica</string>
<string name="aragonese">aragonés</string>
<string name="american_sign_language">lenguaje de signos americano</string>
<string name="kotava">Kotava</string>
<string name="aymara">Aymara</string>
<string name="bambara">Bambara</string>
<string name="belarusian">Bielorruso</string>
<string name="bengali">bengalí</string>
<string name="british_sign_language">Lenguaje de señas británico</string>
<string name="tibetan">Tibetano</string>
<string name="bulgarian">búlgaro</string>
<string name="catalan">Catalán</string>
<string name="chamorro">Chamorro</string>
<string name="chuvash">Chuvash</string>
<string name="cornish">Córnico</string>
<string name="corsican">Corso</string>
<string name="esperanto">Esperanto</string>
<string name="ewe">Oveja</string>
<string name="faroese">Feroés</string>
<string name="french">Francés</string>
<string name="scottish_gaelic">Gaélico escocés</string>
<string name="manx">lengua de la isla de Man</string>
<string name="german_sign_language">Lengua de señas alemana</string>
<string name="gujarati">Gujarati</string>
<string name="haitian">Haitiano</string>
<string name="hausa">Hausa</string>
<string name="herero">Herero</string>
<string name="hiri_motu">Hiri Motu</string>
<string name="croatian">Croata</string>
<string name="armenian">Armenio</string>
<string name="inuktitut">Inuktitut</string>
<string name="inupiaq">Inupiaq</string>
<string name="javanese">javanés</string>
<string name="lojban">Lojban</string>
<string name="japanese_sign_language">Lengua de señas japonesa</string>
<string name="kannada">Canarés</string>
<string name="kashmiri">Cachemira</string>
<string name="kanuri">Kanuri</string>
<string name="khmer">Jemer</string>
<string name="kikuyu">Kikuyu</string>
<string name="kinyarwanda">Kinyarwanda</string>
<string name="kongo">Congo</string>
<string name="kuanyama">Animales</string>
<string name="kurdish">kurdo</string>
<string name="lao">Lao</string>
<string name="limburgan">Limburgan</string>
<string name="lithuanian">Lituano</string>
<string name="luxembourgish">Luxemburgués</string>
<string name="luba_katanga">Luba-Katanga</string>
<string name="ganda">Ganda</string>
<string name="marshallese">Marshalés</string>
<string name="malayalam">Malayalam</string>
<string name="malagasy">Madagascarí</string>
<string name="maori">Maori</string>
<string name="nauru">Nauru</string>
<string name="south_ndebele">Ndebele del sur</string>
<string name="north_ndebele">Ndebele del norte</string>
<string name="norwegian">Noruego</string>
<string name="nyanja">Nyanja</string>
<string name="occitan">Occitano</string>
<string name="oromo">Oromo</string>
<string name="ossetian">Osetio</string>
<string name="panjabi">Panjabi</string>
<string name="pakistan_sign_language">Lengua de señas pakistaní</string>
<string name="pushto">Pushto</string>
<string name="quechua">Quechua</string>
<string name="romanian">Rumano</string>
<string name="russian">Ruso</string>
<string name="sango">Sango</string>
<string name="sinhala">Cingalés</string>
<string name="slovak">Eslovaco</string>
<string name="slovenian">Esloveno</string>
<string name="shona">Shona</string>
<string name="spanish">Español</string>
<string name="sardinian">Sardo</string>
<string name="serbian">Serbio</string>
<string name="swati">Swati</string>
<string name="sundanese">Sundanés</string>
<string name="tahitian">Tahitiano</string>
<string name="tamil">Tamil</string>
<string name="tajik">Tayiko</string>
<string name="tagalog">Tagalo</string>
<string name="thai">Tailandés</string>
<string name="tigrinya">Tigrinya</string>
<string name="klingon">Klingon</string>
<string name="tswana">Tswana</string>
<string name="turkish">Turco</string>
<string name="ukrainian">Ucranio</string>
<string name="urdu">Urdu</string>
<string name="uzbek">Uzbeko</string>
<string name="venda">Venda</string>
<string name="walloon">Valonia</string>
<string name="xhosa">Xhosa</string>
<string name="zhuang">Zhuang</string>
<string name="zulu">Zulú</string>
<string name="bynd">Atribución: sin derivados</string>
<string name="bync">Atribución - No comercial</string>
<string name="public_domain">Dedicación de dominio público</string>
<string name="unlisted">No listado</string>
<string name="unknown_host">Host desconocido</string>
<string name="stream_title_error">El título no debe estar vacío</string>
<string name="network_error">No se encontró conexión a Internet</string>
<string name="unknwon_error">Error desconocido</string>
<string name="instance_error">La instancia no puede estar vacía</string>
<string name="malformed_instance_error">La instancia debe tener una URL válida</string>
<string name="delete_account">¿Eliminar la cuenta %s asociada con el servidor %s \?</string>
<string name="password_error">La contraseña no puede estar vacía</string>
<string name="permissions">Se necesita acceso a la cámara y al micrófono para transmitir en vivo. Haga clic a continuación para cambiar la configuración de permisos.</string>
<string name="tags_rules">Máximo 5 etiquetas, cada una de entre 2 y 30 caracteres, separadas por comas</string>
<string name="background_reason">Tu transmisión en vivo finalizó porque la aplicación pasó a segundo plano</string>
<string name="lock_reason">Tu transmisión en vivo terminó porque el teléfono estaba bloqueado</string>
<string name="live_disabled">Esta instancia ha inhabilitado la transmisión en vivo.</string>
<string name="no">No</string>
<string name="cancel">Cancelar</string>
<string name="stream_title">Título</string>
<string name="stream_licence">Licencia</string>
<string name="password">Contraseña</string>
<string name="save_replay">Publica automáticamente una repetición cuando finaliza tu transmisión en vivo</string>
<string name="activism">Activismo</string>
<string name="afrikaans">africaans</string>
<string name="brazilian_sign_language">Lenguaje de señas brasileño</string>
<string name="czech">Checo</string>
<string name="advanced_settings">▶ Ajustes avanzados</string>
<string name="chechen">Checheno</string>
<string name="danish">Danés</string>
<string name="german">Alemán</string>
<string name="fulah">Fulah</string>
<string name="hindi">Hindi</string>
<string name="connection">Conexión</string>
<string name="download_enabled">Habilitar descarga</string>
<string name="comments_enabled">Habilitar comentarios de video</string>
<string name="creating">Espera la creación en vivo</string>
<string name="sports">Deportes</string>
<string name="people">Gente</string>
<string name="entertainment">Entretenimiento</string>
<string name="stream_ended">La transmisión en vivo terminó</string>
<string name="stream_resolution">Resolución de transmisión en vivo</string>
<string name="vehicles">Vehículos</string>
<string name="travels">Viajes</string>
<string name="gaming">Juegos</string>
<string name="comedy">Comedia</string>
<string name="news_politics">Noticias y política</string>
<string name="animals">Animales</string>
<string name="kids">Niños</string>
<string name="assamese">Asamés</string>
<string name="avaric">Avarico</string>
<string name="bashkir">Bashkir</string>
<string name="bislama">Bislama</string>
<string name="breton">Bretón</string>
<string name="cree">Cree</string>
<string name="czech_sign_language">Lengua de señas checa</string>
<string name="chinese_sign_language">Lenguaje de señas chino</string>
<string name="welsh">Galés</string>
<string name="dhivehi">Dhivehi</string>
<string name="greek">Griego</string>
<string name="estonian">Estonio</string>
<string name="basque">Vasco</string>
<string name="persian">Persa</string>
<string name="fijian">Fiyiano</string>
<string name="french_sign_language">Lengua de señas francesa</string>
<string name="galician">Gallego</string>
<string name="abkhazian">Abjasio</string>
<string name="azerbaijani">Azerbaiyano</string>
<string name="bosnian">bosnio</string>
<string name="danish_sign_language">Lengua de señas danesa</string>
<string name="dzongkha">Dzongkha</string>
<string name="english">Inglés</string>
<string name="western_frisian">Frisón occidental</string>
<string name="guarani">Guaraní</string>
<string name="serbo_croatian">Serbocroata</string>
<string name="finnish">Finlandés</string>
<string name="irish">Irlandesa</string>
<string name="hungarian">Húngaro</string>
<string name="igbo">Igbo</string>
<string name="indonesian">Indonesio</string>
<string name="japanese">Japonés</string>
<string name="kabyle">Kabyle</string>
<string name="kalaallisut">Kalaallisut</string>
<string name="georgian">Georgiano</string>
<string name="kazakh">Kazajo</string>
<string name="kirghiz">Kirguís</string>
<string name="korean">Coreano</string>
<string name="latvian">Letón</string>
<string name="hebrew">Hebreo</string>
<string name="sichuan_yi">Sichuan Yi</string>
<string name="icelandic">Islandés</string>
<string name="komi">Komi</string>
<string name="lingala">Lingala</string>
<string name="maltese">Maltese</string>
<string name="italian">Italiano</string>
<string name="ojibwa">Ojibwa</string>
<string name="portuguese">Portugués</string>
<string name="marathi">Marathi</string>
<string name="macedonian">Macedónio</string>
<string name="mongolian">Mongol</string>
<string name="malay_macrolanguage">Malayo (macrolenguaje)</string>
<string name="southern_sotho">Sur de Sotho</string>
<string name="telugu">Telugu</string>
<string name="tsonga">Tsonga</string>
<string name="wolof">Wolof</string>
<string name="yiddish">Yídish</string>
<string name="byncnd">Atribución - No comercial - Sin derivados</string>
<string name="app_name">PeerTube Live</string>
<string name="burmese">Birmano</string>
<string name="navajo">Navajo</string>
<string name="ndonga">Ndonga</string>
<string name="nepali_macrolanguage">Nepalí (macrolenguaje)</string>
<string name="dutch">Holandés</string>
<string name="oriya_macrolanguage">Oriya (macrolenguaje)</string>
<string name="polish">Polaco</string>
<string name="russian_sign_language">Lengua de señas rusa</string>
<string name="saudi_arabian_sign_language">Lengua de señas saudita</string>
<string name="sindhi">Sindhi</string>
<string name="norwegian_nynorsk">Noruego Nynorsk</string>
<string name="norwegian_bokmål">Noruego bokmål</string>
<string name="rundi">Rundi</string>
<string name="swahili_macrolanguage">Swahili (macrolenguaje)</string>
<string name="tatar">Tártaro</string>
<string name="vietnamese">Vietnamita</string>
<string name="byncsa">Atribución - No comercial - Compartir por igual</string>
<string name="privacy_private">Privado</string>
<string name="romansh">Romanche</string>
<string name="south_african_sign_language">Lenguaje de señas sudafricano</string>
<string name="samoan">Samoano</string>
<string name="northern_sami">Sami del norte</string>
<string name="somali">Somalí</string>
<string name="albanian">Albanés</string>
<string name="swedish">Sueco</string>
<string name="swedish_sign_language">Lenguaje de señas sueco</string>
<string name="tonga_tonga_islands">Tonga (Islas Tonga)</string>
<string name="turkmen">Turcomano</string>
<string name="twi">Twi</string>
<string name="uighur">Uigur</string>
<string name="yoruba">Yoruba</string>
<string name="chinese">Chino</string>
<string name="by">Atribución</string>
<string name="bysa">Atribución - Compartir igual</string>
<string name="privacy_public">Público</string>
<string name="internal">Interno</string>
</resources>

View File

@ -10,6 +10,8 @@
<string name="instance_error">L\'instance ne peut pas être vide</string>
<string name="username_error">Le nom d\'utilisateur ne peut pas être vide</string>
<string name="password_error">Le mot de passe ne peut pas être vide</string>
<string name="two_fa_error">Le jeton d\'authentification à deux facteurs ne peut pas être vide</string>
<string name="invalid_two_fa_token">Le jeton d\'authentification à deux facteurs n\'est pas valide</string>
<string name="malformed_instance_error">L\'instance doit avoir une URL valide</string>
<string name="account_exist">Ce compte existe déjà</string>
<!-- messages -->
@ -48,6 +50,7 @@
<string name="username">Nom d\'utilisateur</string>
<string name="password">Mot de passe</string>
<string name="instance">Instance</string>
<string name="two_fa">Jeton d\'authentification à deux facteurs</string>
<string name="delete_account_title">Supprimer ce compte</string>
<string name="comments_enabled">Activer les commentaires</string>
<string name="download_enabled">Activer le téléchargement</string>
@ -166,7 +169,7 @@
<string name="kabyle">Kabyle</string>
<string name="kalaallisut">Groenlandais</string>
<string name="kannada">Kannada</string>
<string name="kashmiri">Kashmiri</string>
<string name="kashmiri">Cachemire</string>
<string name="georgian">Géorgien</string>
<string name="kanuri">Kanouri</string>
<string name="kazakh">Kazakh</string>

View File

@ -0,0 +1,281 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="go_live">Iniciar emisión en directo</string>
<string name="connect">Conectar</string>
<string name="advanced_settings">▶ Axustes avanzados</string>
<string name="advanced_settings_expand">▼ Axustes avanzados</string>
<string name="connection">Conexión</string>
<string name="comedy">Comedia</string>
<string name="amharic">Amharic</string>
<string name="aragonese">Aragonés</string>
<string name="stream_title_error">O título non pode quedar baleiro</string>
<string name="instance_error">Hai que indicar unha instancia</string>
<string name="username_error">Debes escribir un nome de usuaria</string>
<string name="password_error">Hai que escribir o contrasinal</string>
<string name="username">Nome de usuaria</string>
<string name="guarani">Guaraní</string>
<string name="german_sign_language">Idioma de signos alemán</string>
<string name="indonesian">Indonesio</string>
<string name="inupiaq">Inupiaq</string>
<string name="limburgan">Limburgan</string>
<string name="ukrainian">Ucraniano</string>
<string name="urdu">Urdu</string>
<string name="uzbek">Uzbek</string>
<string name="app_name">PeerTube Live</string>
<string name="unknwon_error">Erro descoñecido</string>
<string name="network_error">Non se detecta conexión a internet</string>
<string name="unknown_host">Servidor descoñecido</string>
<string name="json_error">Erro JSON</string>
<string name="loading_channels">Cargando a túa lista de canles</string>
<string name="account_exist">Esta conta xa existe</string>
<string name="malformed_instance_error">Escribe un URL válido para a instancia</string>
<string name="no_instance">Sen conta rexistrada. Engade unha conta PeerTube usando o \'+\' na barra superior</string>
<string name="try_connect">Agardando pola conexión</string>
<string name="permissions">Precisa acceso á cámara e micrófono para retransmitir. Preme embaixo para conceder permiso.</string>
<string name="delete_account">Eliminar a conta %s asociada ao servidor %s\?</string>
<string name="tags_rules">Máximo 5 etiquetas, de entre 2 e 30 caracteres, separadas por vírgulas</string>
<string name="back_reason">A emisión en directo rematou após premer o botón atrás</string>
<string name="background_reason">A emisión en directo rematou porque a app pasou ao segundo plano</string>
<string name="stop_reason">Rematou a emisión en directo</string>
<string name="ask_end_stream">Queres deter a emisión en directo\?</string>
<string name="entertainment">Entretemento</string>
<string name="how_to">Instruccións</string>
<string name="education">Educación</string>
<string name="bashkir">Bashkir</string>
<string name="brazilian_sign_language">Idioma de signos brasileiro</string>
<string name="faroese">Faroese</string>
<string name="galician">Galego</string>
<string name="save_replay_info">Se activas esta opción, o teu directo rematará se excede a túa cota de vídeo</string>
<string name="lock_reason">A emisión en directo rematou porque se bloqueou o teléfono</string>
<string name="network_reason">A emisión en directo rematou porque houbo problemas coa rede</string>
<string name="live_disabled">A instancia desactivou a emisión en directo.</string>
<string name="stream_title">Título</string>
<string name="stream_privacy">Privacidade</string>
<string name="choose_channel">Canle</string>
<string name="cancel">Cancelar</string>
<string name="yes">Si</string>
<string name="no">Non</string>
<string name="stream_category">Categoría</string>
<string name="stream_language">Idioma</string>
<string name="stream_licence">Licenza</string>
<string name="goto_permissions">Ver axustes</string>
<string name="gaming">Xogos</string>
<string name="afar">Afar</string>
<string name="danish_sign_language">Idioma de signos danés</string>
<string name="add_instance">Engadir esta conta</string>
<string name="password">Contrasinal</string>
<string name="stream_resolution">Resolución da emisión en directo</string>
<string name="end_stream">Deter emisión en directo</string>
<string name="art">Arte</string>
<string name="sports">Deporte</string>
<string name="travels">Viaxes</string>
<string name="people">Persoas</string>
<string name="news_politics">Novas e política</string>
<string name="activism">Activismo</string>
<string name="instance">Instancia</string>
<string name="delete_account_title">Eliminar esta conta</string>
<string name="comments_enabled">Activar comentarios ao vídeo</string>
<string name="download_enabled">Permitir descarga</string>
<string name="nsfw">Contén contido sensible</string>
<string name="tags">Etiquetas</string>
<string name="description">Descrición</string>
<string name="save_replay">Publicar automáticamente unha repetición ao rematar o directo</string>
<string name="stream_ended">Rematou a emisión en directo</string>
<string name="creating">Agardar pola creación do directo</string>
<string name="exemple_instance">ex. peertube.fr, https://peertube.fr</string>
<string name="music">Música</string>
<string name="films">Películas</string>
<string name="akan">Akan</string>
<string name="american_sign_language">Idioma de signos americano</string>
<string name="breton">Bretón</string>
<string name="pushto">Pushto</string>
<string name="vehicles">Vehículos</string>
<string name="science_tech">Ciencia e Tecnoloxía</string>
<string name="animals">Animais</string>
<string name="kids">Rapazada</string>
<string name="food">Comida</string>
<string name="abkhazian">Abkhazian</string>
<string name="afrikaans">Afrikaans</string>
<string name="arabic">Árabe</string>
<string name="assamese">Assamese</string>
<string name="avaric">Avaric</string>
<string name="aymara">Aymara</string>
<string name="bislama">Bislama</string>
<string name="bulgarian">Búlgaro</string>
<string name="chamorro">Chamorro</string>
<string name="kotava">Kotava</string>
<string name="bambara">Bambara</string>
<string name="belarusian">Belarusian</string>
<string name="british_sign_language">Idioma de signos británico</string>
<string name="azerbaijani">Azerbaijani</string>
<string name="bengali">Bengali</string>
<string name="tibetan">Tibetan</string>
<string name="bosnian">Bosnian</string>
<string name="welsh">Galés</string>
<string name="estonian">Estonio</string>
<string name="catalan">Catalán</string>
<string name="czech">Checo</string>
<string name="chechen">Checheno</string>
<string name="chuvash">Chuvash</string>
<string name="cornish">Cornish</string>
<string name="corsican">Corso</string>
<string name="cree">Cree</string>
<string name="danish">Danés</string>
<string name="czech_sign_language">Idioma de signos checo</string>
<string name="chinese_sign_language">Idioma de signos chinés</string>
<string name="dhivehi">Dhivehi</string>
<string name="german">Alemán</string>
<string name="persian">Persa</string>
<string name="finnish">Finlandés</string>
<string name="western_frisian">Frisio occidental</string>
<string name="french_sign_language">Idioma de signos francés</string>
<string name="fulah">Fulah</string>
<string name="dzongkha">Dzongkha</string>
<string name="greek">Grego</string>
<string name="basque">Euskera</string>
<string name="ewe">Ewe</string>
<string name="fijian">Fijian</string>
<string name="french">Francés</string>
<string name="english">Inglés</string>
<string name="esperanto">Esperanto</string>
<string name="kikuyu">Kikuyu</string>
<string name="scottish_gaelic">Gaélico escocés</string>
<string name="croatian">Croata</string>
<string name="irish">Irlandés</string>
<string name="manx">Manx</string>
<string name="gujarati">Gujarati</string>
<string name="haitian">Haitiano</string>
<string name="hausa">Hausa</string>
<string name="serbo_croatian">Serbo-Croata</string>
<string name="hebrew">Hebreo</string>
<string name="hindi">Hindi</string>
<string name="hiri_motu">Hiri Motu</string>
<string name="herero">Herero</string>
<string name="kinyarwanda">Kinyarwanda</string>
<string name="burmese">Burmese</string>
<string name="ojibwa">Ojibwa</string>
<string name="hungarian">Húngaro</string>
<string name="armenian">Armenio</string>
<string name="kalaallisut">Kalaallisut</string>
<string name="kazakh">Kazakh</string>
<string name="lingala">Lingala</string>
<string name="ganda">Ganda</string>
<string name="macedonian">Macedonian</string>
<string name="navajo">Navajo</string>
<string name="nepali_macrolanguage">Nepali (macrolanguage)</string>
<string name="igbo">Igbo</string>
<string name="sichuan_yi">Sichuan Yi</string>
<string name="icelandic">Islandés</string>
<string name="javanese">Javanese</string>
<string name="inuktitut">Inuktitut</string>
<string name="japanese">Xaponés</string>
<string name="italian">Italiano</string>
<string name="kabyle">Kabyle</string>
<string name="khmer">Khmer</string>
<string name="kirghiz">Kirghiz</string>
<string name="lojban">Lojban</string>
<string name="japanese_sign_language">Idioma de signos xaponés</string>
<string name="kannada">Kannada</string>
<string name="kashmiri">Kashmiri</string>
<string name="georgian">Georgian</string>
<string name="kanuri">Kanuri</string>
<string name="kongo">Kongo</string>
<string name="komi">Komi</string>
<string name="korean">Coreano</string>
<string name="kurdish">Curdo</string>
<string name="latvian">Letón</string>
<string name="luba_katanga">Luba-Katanga</string>
<string name="malayalam">Malayalam</string>
<string name="maltese">Maltés</string>
<string name="kuanyama">Kuanyama</string>
<string name="lao">Lao</string>
<string name="lithuanian">Lituano</string>
<string name="luxembourgish">Luxemburgués</string>
<string name="marshallese">Mashallese</string>
<string name="marathi">Marathi</string>
<string name="malagasy">Malagsy</string>
<string name="maori">Maori</string>
<string name="mongolian">Mongolian</string>
<string name="malay_macrolanguage">Malay (macrolanguage)</string>
<string name="nauru">Nauru</string>
<string name="ndonga">Ndonga</string>
<string name="norwegian_nynorsk">Noruegués Nynorsk</string>
<string name="norwegian_bokmål">Noruegués Bokmål</string>
<string name="norwegian">Noruegués</string>
<string name="occitan">Occitano</string>
<string name="ossetian">Ossetian</string>
<string name="dutch">Neerlandés</string>
<string name="sinhala">Sinhala</string>
<string name="thai">Thai</string>
<string name="bynd">Atribución - Sen derivados</string>
<string name="nyanja">Nyanja</string>
<string name="oriya_macrolanguage">Oriya (macrolanguage)</string>
<string name="slovak">Eslovaco</string>
<string name="slovenian">Esloveno</string>
<string name="oromo">Oromo</string>
<string name="south_african_sign_language">Idioma de singos de África do sur</string>
<string name="swedish_sign_language">Idioma de signos sueco</string>
<string name="panjabi">Panjabi</string>
<string name="polish">Polaco</string>
<string name="portuguese">Portugués</string>
<string name="quechua">Quechua</string>
<string name="romansh">Romansh</string>
<string name="russian_sign_language">Idioma de signos ruso</string>
<string name="rundi">Rundi</string>
<string name="pakistan_sign_language">Idioma de signos de Paquistán</string>
<string name="romanian">Rumanés</string>
<string name="russian">Ruso</string>
<string name="sango">Sango</string>
<string name="saudi_arabian_sign_language">Idioma de signos de Arabia Saudita</string>
<string name="northern_sami">Sami do Norte</string>
<string name="shona">Shona</string>
<string name="spanish">Español</string>
<string name="samoan">Samoano</string>
<string name="sindhi">Sindhi</string>
<string name="somali">Somalí</string>
<string name="southern_sotho">Sotho do Sur</string>
<string name="swahili_macrolanguage">Swahili (macrolanguage)</string>
<string name="tamil">Tamil</string>
<string name="turkmen">Turkmen</string>
<string name="turkish">Turco</string>
<string name="twi">Twi</string>
<string name="vietnamese">Vietnamita</string>
<string name="by">Atribución</string>
<string name="byncnd">Atribución - Non Comercial - Sen Derivados</string>
<string name="public_domain">Dedicado ao Dominio Público</string>
<string name="privacy_public">Público</string>
<string name="internal">Interno</string>
<string name="albanian">Albanés</string>
<string name="sardinian">Sardo</string>
<string name="serbian">Serbio</string>
<string name="swati">Swati</string>
<string name="sundanese">Sundanese</string>
<string name="tahitian">Tahitian</string>
<string name="telugu">Telugu</string>
<string name="tajik">Tajik</string>
<string name="tagalog">Tagalog</string>
<string name="tigrinya">Tigrinya</string>
<string name="klingon">Klingon</string>
<string name="tonga_tonga_islands">Tonga (Illas Tonga)</string>
<string name="swedish">Sueco</string>
<string name="tatar">Tatar</string>
<string name="venda">Venda</string>
<string name="walloon">Walloon</string>
<string name="bysa">Atribución - Compartir Igual</string>
<string name="tswana">Tswana</string>
<string name="tsonga">Tsonga</string>
<string name="uighur">Uighur</string>
<string name="wolof">Wolof</string>
<string name="xhosa">Xhosa</string>
<string name="yiddish">Yiddish</string>
<string name="yoruba">Yoruba</string>
<string name="zhuang">Zhuang</string>
<string name="chinese">Chinés</string>
<string name="bync">Atribución - Non Comercial</string>
<string name="zulu">Zulu</string>
<string name="byncsa">Atribución - Non Comercial - Compartir Igual</string>
<string name="unlisted">Fóra de listaxes</string>
<string name="privacy_private">Privado</string>
<string name="south_ndebele">Ndebele do sur</string>
<string name="north_ndebele">Ndebele do norte</string>
</resources>

View File

@ -0,0 +1,280 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="username_error">A felhasználónév nem lehet üres</string>
<string name="instance_error">A példány nem lehet üres</string>
<string name="loading_channels">Saját csatornalista betöltése</string>
<string name="unknwon_error">Ismeretlen hiba</string>
<string name="unknown_host">Ismeretlen kiszolgáló</string>
<string name="json_error">JSON hiba</string>
<string name="stream_title_error">A cím nem lehet üres</string>
<string name="password_error">A jelszó nem lehet üres</string>
<string name="malformed_instance_error">A példánynak érvényes URL-nek kell lennie</string>
<string name="account_exist">Ez a fiók már létezik</string>
<string name="no_instance">Nincs fiók regisztrálva. Adjon hozzá egy PeerTube-fiókot a felső sávban lévő „+” gombbal</string>
<string name="try_connect">Várakozás a kapcsolatra</string>
<string name="permissions">Az elő adáshoz kamera- és mikrofon-hozzáférési engedély szükséges. Kattintson lent az engedélybeállítások módosításához.</string>
<string name="tags_rules">Legfeljebb 5 címke, mindegyik 2 és 30 karakter közti hosszúsággal, vesszőkkel elválasztva</string>
<string name="save_replay_info">Ha engedélyezi ezt a beállítást, akkor az elő adás megszakad, ha túllépi a videókvótáját</string>
<string name="network_reason">Az elő adása hálózati probléma miatt befejeződött</string>
<string name="live_disabled">Ez a példány letiltotta az élő adásokat.</string>
<string name="choose_channel">Csatorna</string>
<string name="go_live">Élő adás indítása</string>
<string name="stream_licence">Licenc</string>
<string name="advanced_settings">▶ Speciális beállítások</string>
<string name="add_instance">Fiók hozzáadása</string>
<string name="music">Zene</string>
<string name="save_replay">Felvétel automatikus közzététele, ha az élő adás véget ér</string>
<string name="stream_ended">Az élő adás véget ért</string>
<string name="end_stream">Élő adás leállítása</string>
<string name="creating">Várakozás az élő adás létrehozására</string>
<string name="stream_resolution">Élő adás felbontása</string>
<string name="exemple_instance">például peertube.fr, https://peertube.fr</string>
<string name="films">Filmek</string>
<string name="vehicles">Járművek</string>
<string name="art">Művészet</string>
<string name="sports">Sport</string>
<string name="travels">Utazás</string>
<string name="gaming">Játék</string>
<string name="people">Emberek</string>
<string name="comedy">Humor</string>
<string name="entertainment">Szórakoztatás</string>
<string name="news_politics">Hírek és politika</string>
<string name="how_to">Hogyanok</string>
<string name="education">Oktatás</string>
<string name="activism">Aktivizmus</string>
<string name="science_tech">Tudomány és technológia</string>
<string name="animals">Állatok</string>
<string name="kids">Gyerekek</string>
<string name="food">Ételek</string>
<string name="afar">Afar</string>
<string name="abkhazian">Abház</string>
<string name="afrikaans">Afrikaans</string>
<string name="akan">Akan</string>
<string name="amharic">Amhara</string>
<string name="arabic">Arab</string>
<string name="aragonese">Aragóniai</string>
<string name="american_sign_language">Amerikai jelnyelv</string>
<string name="assamese">Asszámi</string>
<string name="avaric">Kaukázusi avar</string>
<string name="kotava">Kotava</string>
<string name="aymara">Ajmara</string>
<string name="azerbaijani">Azeri</string>
<string name="bashkir">Baskír</string>
<string name="bambara">Bambara</string>
<string name="belarusian">Belarusz</string>
<string name="bengali">Bengáli</string>
<string name="british_sign_language">Brit jelnyelv</string>
<string name="bislama">Biszlama</string>
<string name="tibetan">Tibeti</string>
<string name="bosnian">Bosnyák</string>
<string name="breton">Breton</string>
<string name="bulgarian">Bolgár</string>
<string name="brazilian_sign_language">Brazil jelnyelv</string>
<string name="catalan">Katalán</string>
<string name="czech">Cseh</string>
<string name="chechen">Csecsen</string>
<string name="chuvash">Csuvas</string>
<string name="cornish">Korni</string>
<string name="corsican">Korzikai</string>
<string name="czech_sign_language">Cseh jelnyelv</string>
<string name="chinese_sign_language">Kínai jelnyelv</string>
<string name="welsh">Walesi</string>
<string name="cree">Krí</string>
<string name="chamorro">Csamoró</string>
<string name="danish">Dán</string>
<string name="german">Német</string>
<string name="danish_sign_language">Dán jelnyelv</string>
<string name="greek">Görög</string>
<string name="english">Angol</string>
<string name="esperanto">Eszperantó</string>
<string name="estonian">Észt</string>
<string name="basque">Baszk</string>
<string name="ewe">Ewe</string>
<string name="faroese">Feröeri</string>
<string name="persian">Perzsa</string>
<string name="finnish">Finn</string>
<string name="french">Francia</string>
<string name="western_frisian">Nyugati fríz</string>
<string name="fijian">Fidzsi</string>
<string name="french_sign_language">Francia jelnyelv</string>
<string name="dhivehi">Divehi</string>
<string name="dzongkha">Dzsonga</string>
<string name="fulah">Ful</string>
<string name="scottish_gaelic">Skót gael</string>
<string name="irish">Ír</string>
<string name="indonesian">Indonéz</string>
<string name="inupiaq">Inupiak</string>
<string name="komi">Komi</string>
<string name="kalaallisut">Grönlandi</string>
<string name="kikuyu">Kikuju</string>
<string name="limburgan">Limburgi</string>
<string name="ojibwa">Odzsibva</string>
<string name="pushto">Pastu</string>
<string name="app_name">PeerTube Live</string>
<string name="network_error">Nincs internetkapcsolat</string>
<string name="zulu">Zulu</string>
<string name="by">Nevezd meg!</string>
<string name="bysa">Nevezd meg! Így add tovább!</string>
<string name="bynd">Nevezd meg! Ne változtasd!</string>
<string name="bync">Nevezd meg! Ne add el!</string>
<string name="byncsa">Nevezd meg! Ne add el! Így add tovább!</string>
<string name="byncnd">Nevezd meg! Ne add el! Ne változtasd!</string>
<string name="public_domain">Közkincsnek jelölt</string>
<string name="privacy_public">Nyilvános</string>
<string name="unlisted">Nem felsorolt</string>
<string name="privacy_private">Privát</string>
<string name="internal">Belső</string>
<string name="ukrainian">Ukrán</string>
<string name="urdu">Urdu</string>
<string name="uzbek">Üzbég</string>
<string name="venda">Venda</string>
<string name="vietnamese">Vietnámi</string>
<string name="walloon">Vallon</string>
<string name="wolof">Volof</string>
<string name="xhosa">Xhosza</string>
<string name="yiddish">Jiddis</string>
<string name="delete_account">Törli a(z) %s fiókot, amely a(z) %s kiszolgálóhoz kapcsolódik\?</string>
<string name="lock_reason">Az elő adása befejeződött, mert a telefon zárolva lett</string>
<string name="background_reason">Az élő adása befejeződött, mert az alkalmazás a háttérbe került</string>
<string name="ask_end_stream">Leállítja az élő adást\?</string>
<string name="back_reason">Az elő adása befejeződött, miután megnyomta a vissza gombot</string>
<string name="stop_reason">Az élő adása befejeződött</string>
<string name="stream_privacy">Adatvédelem</string>
<string name="cancel">Mégse</string>
<string name="yes">Igen</string>
<string name="no">Nem</string>
<string name="goto_permissions">Beállítások megtekintése</string>
<string name="instance">Példány</string>
<string name="tags">Címkék</string>
<string name="description">Leírás</string>
<string name="connect">Kapcsolódás</string>
<string name="stream_title">Cím</string>
<string name="stream_category">Kategória</string>
<string name="connection">Kapcsolat</string>
<string name="stream_language">Nyelv</string>
<string name="username">Felhasználónév</string>
<string name="advanced_settings_expand">▼ Speciális beállítások</string>
<string name="nsfw">Érzékeny tartalmat tartalmaz</string>
<string name="password">Jelszó</string>
<string name="delete_account_title">Példány törlése</string>
<string name="comments_enabled">Videó hozzászólásainak engedélyezése</string>
<string name="download_enabled">Letöltés engedélyezése</string>
<string name="galician">Galiciai</string>
<string name="manx">Manx</string>
<string name="guarani">Guarani</string>
<string name="german_sign_language">Német jelnyelv</string>
<string name="gujarati">Gudzsaráti</string>
<string name="haitian">Haiti</string>
<string name="hebrew">Héber</string>
<string name="herero">Herero</string>
<string name="hindi">Hindi</string>
<string name="hiri_motu">Hiri motu</string>
<string name="croatian">Horvát</string>
<string name="hungarian">Magyar</string>
<string name="armenian">Örmény</string>
<string name="hausa">Hausza</string>
<string name="serbo_croatian">Szerbhorvát</string>
<string name="igbo">Igbó</string>
<string name="sichuan_yi">Szecsuani ji</string>
<string name="inuktitut">Inuktitut</string>
<string name="icelandic">Izlandi</string>
<string name="italian">Olasz</string>
<string name="javanese">Jávai</string>
<string name="lojban">Lojban</string>
<string name="japanese">Japán</string>
<string name="kannada">Kannada</string>
<string name="kashmiri">Kasmíri</string>
<string name="japanese_sign_language">Japán jelnyelv</string>
<string name="georgian">Grúz</string>
<string name="kanuri">Kanuri</string>
<string name="kazakh">Kazak</string>
<string name="khmer">Khmer</string>
<string name="kinyarwanda">Kinyarvanda</string>
<string name="kirghiz">Kirgiz</string>
<string name="kabyle">Kabil</string>
<string name="kongo">Kongo</string>
<string name="korean">Koreai</string>
<string name="kurdish">Kurd</string>
<string name="lao">Lao</string>
<string name="latvian">Lett</string>
<string name="lingala">Lingala</string>
<string name="lithuanian">Litván</string>
<string name="luxembourgish">Luxemburgi</string>
<string name="luba_katanga">Luba-katanga</string>
<string name="ganda">Ganda</string>
<string name="marshallese">Marshall-szigeteki</string>
<string name="malayalam">Malajálam</string>
<string name="marathi">Maráthi</string>
<string name="macedonian">Macedón</string>
<string name="malagasy">Malgas</string>
<string name="maltese">Máltai</string>
<string name="mongolian">Mongol</string>
<string name="south_ndebele">Déli ndebele</string>
<string name="north_ndebele">Északi ndebele</string>
<string name="maori">Maori</string>
<string name="malay_macrolanguage">Maláj (makronyelv)</string>
<string name="burmese">Burmai</string>
<string name="nauru">Naurui</string>
<string name="navajo">Navahó</string>
<string name="ndonga">Ndonga</string>
<string name="nepali_macrolanguage">Nepáli (makronyelv)</string>
<string name="dutch">Holland</string>
<string name="norwegian_nynorsk">Norvég nynorsk</string>
<string name="norwegian_bokmål">Norvég bokmål</string>
<string name="norwegian">Norvég</string>
<string name="occitan">Okcitán</string>
<string name="oriya_macrolanguage">Orija (makronyelv)</string>
<string name="oromo">Oromó</string>
<string name="nyanja">Nyandzsa</string>
<string name="ossetian">Oszét</string>
<string name="panjabi">Pandzsábi</string>
<string name="pakistan_sign_language">Pakisztáni jelnyelv</string>
<string name="polish">Lengyel</string>
<string name="portuguese">Portugál</string>
<string name="quechua">Kecsua</string>
<string name="romansh">Romans</string>
<string name="romanian">Román</string>
<string name="russian_sign_language">Orosz jelnyelv</string>
<string name="rundi">Rundi</string>
<string name="russian">Orosz</string>
<string name="sango">Szangó</string>
<string name="south_african_sign_language">Dél-afrikai jelnyelv</string>
<string name="sinhala">Szingaléz</string>
<string name="slovak">Szlovák</string>
<string name="slovenian">Szlovén</string>
<string name="saudi_arabian_sign_language">Szaúd-arábiai jelnyelv</string>
<string name="northern_sami">Északi számi</string>
<string name="samoan">Szamoai</string>
<string name="shona">Sona</string>
<string name="sindhi">Szindhi</string>
<string name="somali">Szomáli</string>
<string name="southern_sotho">Déli szoto</string>
<string name="spanish">Spanyol</string>
<string name="albanian">Albán</string>
<string name="sardinian">Szardíniai</string>
<string name="swahili_macrolanguage">Szuahéli (makronyelv)</string>
<string name="swedish">Svéd</string>
<string name="swedish_sign_language">Svéd jelnyelv</string>
<string name="serbian">Szerb</string>
<string name="swati">Szvázi</string>
<string name="sundanese">Szundanéz</string>
<string name="tahitian">Tahiti</string>
<string name="tamil">Tamil</string>
<string name="tatar">Tatár</string>
<string name="telugu">Telugu</string>
<string name="tajik">Tádzsik</string>
<string name="tonga_tonga_islands">Tonga (Tonga-szigetek)</string>
<string name="tswana">Csvana</string>
<string name="tsonga">Conga</string>
<string name="turkmen">Türkmén</string>
<string name="turkish">Török</string>
<string name="twi">Tvi</string>
<string name="tagalog">Tagalog</string>
<string name="thai">Thai</string>
<string name="tigrinya">Tigrinya</string>
<string name="klingon">Klingon</string>
<string name="uighur">Ujgur</string>
<string name="yoruba">Joruba</string>
<string name="zhuang">Csuang</string>
<string name="chinese">Kínai</string>
</resources>

View File

@ -0,0 +1,281 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">PeerTube Live</string>
<string name="network_error">Nie znaleziono połączenia z Internetem</string>
<string name="unknwon_error">Nieznany błąd</string>
<string name="unknown_host">Nieznany Host</string>
<string name="json_error">Błąd JSON</string>
<string name="stream_title_error">Tytuł nie może być pusty</string>
<string name="instance_error">Instancja nie może być pusta</string>
<string name="live_disabled">Ta instancja ma wyłączone transmisje na żywo.</string>
<string name="stream_ended">Transmisja na żywo zakończona</string>
<string name="malformed_instance_error">Instancja musi mieć poprawny adres URL</string>
<string name="account_exist">To konto już istnieje</string>
<string name="loading_channels">Pobieranie listy kanałów</string>
<string name="try_connect">Oczekiwanie na połączenie</string>
<string name="ask_end_stream">Czy chcesz zatrzymać transmisję\?</string>
<string name="stop_reason">Twoja transmisja na żywo zakończyła się</string>
<string name="back_reason">Twoja transmisja na żywo zakończyła się po wciśnięciu przycisku wstecz</string>
<string name="choose_channel">Kanał</string>
<string name="go_live">Rozpocznij transmisję na żywo</string>
<string name="goto_permissions">Przejdź do ustawień</string>
<string name="connect">Połącz</string>
<string name="stream_title">Tytuł</string>
<string name="stream_category">Kategoria</string>
<string name="stream_privacy">Prywatność</string>
<string name="stream_language">Język</string>
<string name="stream_licence">Licencja</string>
<string name="advanced_settings">▶ Ustawienia zaawansowane</string>
<string name="advanced_settings_expand">▼ Ustawienia zaawansowane</string>
<string name="add_instance">Dodaj to konto</string>
<string name="connection">Połączenie</string>
<string name="username">Nazwa użytkownika</string>
<string name="password">Hasło</string>
<string name="instance">Instancja</string>
<string name="delete_account_title">Usuń to konto</string>
<string name="download_enabled">Zezwól na pobieranie</string>
<string name="nsfw">Zawiera treści wrażliwe</string>
<string name="tags">Tagi</string>
<string name="description">Opis</string>
<string name="save_replay">Automatycznie publikuj powtórkę, gdy transmisja zakończy się</string>
<string name="end_stream">Zatrzymaj transmisję na żywo</string>
<string name="creating">Czekaj na utworzenie transmisji</string>
<string name="stream_resolution">Jakość transmisji na żywo</string>
<string name="music">Muzyka</string>
<string name="films">Filmy</string>
<string name="vehicles">Pojazdy</string>
<string name="art">Sztuka</string>
<string name="sports">Sport</string>
<string name="travels">Podróże</string>
<string name="gaming">Gry</string>
<string name="people">Ludzie</string>
<string name="comedy">Komedia</string>
<string name="entertainment">Rozrywka</string>
<string name="news_politics">Wiadomości i polityka</string>
<string name="how_to">Poradniki</string>
<string name="education">Edukacja</string>
<string name="activism">Aktywizm</string>
<string name="science_tech">Nauka i technologia</string>
<string name="food">Jedzenie</string>
<string name="afar">Afar</string>
<string name="abkhazian">Abchazki</string>
<string name="afrikaans">Afrikaans</string>
<string name="amharic">Amharski</string>
<string name="aragonese">Aragoński</string>
<string name="assamese">Assamski</string>
<string name="avaric">Avaric</string>
<string name="kotava">Kotava</string>
<string name="aymara">Aymara</string>
<string name="azerbaijani">Azerski</string>
<string name="bashkir">Bashkir</string>
<string name="bambara">Bambara</string>
<string name="bengali">Bengali</string>
<string name="british_sign_language">Brytyjski język migowy</string>
<string name="tibetan">Tybetański</string>
<string name="catalan">Kataloński</string>
<string name="czech">Czeski</string>
<string name="chamorro">Chamorro</string>
<string name="chechen">Czeczeński</string>
<string name="chuvash">Czuwasz</string>
<string name="cornish">Kornwalijski</string>
<string name="corsican">Korsykański</string>
<string name="german">Niemiecki</string>
<string name="dhivehi">Dhivehi</string>
<string name="danish_sign_language">Duński język migowy</string>
<string name="dzongkha">Dzongkha</string>
<string name="greek">Grecki</string>
<string name="english">Angielski</string>
<string name="estonian">Estoński</string>
<string name="basque">Baskijski</string>
<string name="ewe">Ewe</string>
<string name="faroese">Farerski</string>
<string name="persian">Perski</string>
<string name="fijian">Fidżi</string>
<string name="finnish">Fiński</string>
<string name="french">Francuski</string>
<string name="western_frisian">Zachodniofryzyjski</string>
<string name="scottish_gaelic">Szkocki Gaelic</string>
<string name="irish">Irlandzki</string>
<string name="manx">Manx</string>
<string name="guarani">Guarani</string>
<string name="german_sign_language">Niemiecki język migowy</string>
<string name="haitian">Haitian</string>
<string name="hausa">Hausa</string>
<string name="serbo_croatian">Sebsko-CHorwacki</string>
<string name="hebrew">Hebrajski</string>
<string name="hindi">Hindi</string>
<string name="hiri_motu">Hiri Motu</string>
<string name="croatian">Chorwacki</string>
<string name="igbo">Igbo</string>
<string name="sichuan_yi">Sichuan Yi</string>
<string name="inupiaq">Inupiaq</string>
<string name="icelandic">Islandzki</string>
<string name="italian">Włoski</string>
<string name="lojban">Lojban</string>
<string name="japanese">japoński</string>
<string name="japanese_sign_language">Japoński język migowy</string>
<string name="kabyle">Kabyle</string>
<string name="kalaallisut">Kalaallisut</string>
<string name="kannada">Kannada</string>
<string name="kashmiri">Kashmiri</string>
<string name="georgian">Gruziński</string>
<string name="kanuri">Kanuri</string>
<string name="kazakh">Kazakh</string>
<string name="kirghiz">Kirghiz</string>
<string name="komi">Komi</string>
<string name="kongo">Kongo</string>
<string name="kuanyama">Kuanyama</string>
<string name="lingala">Lingala</string>
<string name="latvian">Łotewski</string>
<string name="luxembourgish">Luksemburski</string>
<string name="luba_katanga">Luba-Katanga</string>
<string name="ganda">Ganda</string>
<string name="marshallese">Marshall</string>
<string name="malayalam">Malayalam</string>
<string name="marathi">Marathi</string>
<string name="macedonian">Macedoński</string>
<string name="maltese">Maltański</string>
<string name="south_ndebele">Południowe Ndebele</string>
<string name="north_ndebele">Północne Ndebele</string>
<string name="ndonga">Ndonga</string>
<string name="nepali_macrolanguage">Nepalski</string>
<string name="norwegian">Norweski</string>
<string name="romanian">Rumuński</string>
<string name="sango">Sango</string>
<string name="saudi_arabian_sign_language">Język migowy Arabii Saudyjskiej</string>
<string name="south_african_sign_language">Południowoafrykański język migowy</string>
<string name="northern_sami">Północni Lapończycy</string>
<string name="samoan">Samoan</string>
<string name="southern_sotho">Południowe Sotho</string>
<string name="spanish">Hiszpański</string>
<string name="albanian">Albański</string>
<string name="sardinian">Sardyński</string>
<string name="serbian">Serbski</string>
<string name="sundanese">Sundajski</string>
<string name="tahitian">Tahitański</string>
<string name="tamil">Tamilski</string>
<string name="tatar">Tatarski</string>
<string name="telugu">Telugu</string>
<string name="thai">Tajski</string>
<string name="tigrinya">Tigrinya</string>
<string name="tsonga">Tsonga</string>
<string name="turkmen">Turmeński</string>
<string name="yoruba">Yoruba</string>
<string name="zhuang">Zhuang</string>
<string name="chinese">Chiński</string>
<string name="bynd">Uznanie autorstwa - Bez utworów zależnych</string>
<string name="bync">Uznanie autorstwa - Użycie niekomercyjne</string>
<string name="byncnd">Uznanie autorstwa - Użycie niekomercyjne - Bez utworów zależnych</string>
<string name="public_domain">Domena Publiczna</string>
<string name="privacy_public">Publiczne</string>
<string name="privacy_private">Prywatne</string>
<string name="unlisted">Niepubliczne</string>
<string name="internal">Wewnętrzne</string>
<string name="username_error">Nazwa użytkownika nie może być pusta</string>
<string name="password_error">Hasło nie może być puste</string>
<string name="no_instance">Brak zarejestrowanego konta. Dodaj konto PeerTube używając \'+\' na górnym pasku</string>
<string name="permissions">Do transmisji na żywo potrzebny jest dostęp do kamery i mikrofonu. Kliknij poniżej, aby zmienić ustawienia uprawnień.</string>
<string name="delete_account">Usunąć konto %s powiązane z serwerem %s\?</string>
<string name="tags_rules">Maksymalnie 5 tagów, każdy o długości od 2 do 30 znaków, oddzielone przecinkiem</string>
<string name="cancel">Anuluj</string>
<string name="yes">Tak</string>
<string name="background_reason">Twoja transmisja na żywo zakończyła się, ponieważ aplikacja przeszła do pracy w tle</string>
<string name="lock_reason">Twoja transmisja na żywo zakończyła się, ponieważ telefon został zablokowany</string>
<string name="network_reason">Twoja transmisja na żywo zakończyła się z powodu problemów z siecią</string>
<string name="no">Nie</string>
<string name="comments_enabled">Włącz komentarze do filmu</string>
<string name="galician">Galicyjski</string>
<string name="gujarati">Gujarati</string>
<string name="exemple_instance">np. peertube.fr, https://peertube.fr</string>
<string name="hungarian">Węgierski</string>
<string name="akan">Akan</string>
<string name="bosnian">Bośniacki</string>
<string name="bulgarian">Bułgarski</string>
<string name="belarusian">Białoruski</string>
<string name="animals">Zwierzęta</string>
<string name="kids">Dzieci</string>
<string name="arabic">Arabski</string>
<string name="american_sign_language">Amerykański język migowy</string>
<string name="bislama">Bislama</string>
<string name="breton">Bretoński</string>
<string name="brazilian_sign_language">Brazylijski język migowy</string>
<string name="danish">Duński</string>
<string name="esperanto">Esperanto</string>
<string name="herero">Herero</string>
<string name="lithuanian">Litewski</string>
<string name="maori">Maori</string>
<string name="panjabi">Pendjabi</string>
<string name="polish">Polski</string>
<string name="portuguese">Portugalski</string>
<string name="rundi">Rundi</string>
<string name="cree">Cree</string>
<string name="czech_sign_language">Czeski język migowy</string>
<string name="chinese_sign_language">Chiński język migowy</string>
<string name="welsh">Walijski</string>
<string name="french_sign_language">Francuski język migowy</string>
<string name="fulah">Fulah</string>
<string name="armenian">Armeński</string>
<string name="inuktitut">Inuktitut</string>
<string name="indonesian">Indonesian</string>
<string name="javanese">Javanese</string>
<string name="limburgan">Limburgan</string>
<string name="malagasy">Malgaski</string>
<string name="norwegian_nynorsk">Norweski Nynorsk</string>
<string name="nyanja">Nyanja</string>
<string name="kikuyu">Kikuyu</string>
<string name="khmer">Khmer</string>
<string name="kinyarwanda">Rwanda</string>
<string name="korean">Koreański</string>
<string name="kurdish">Kurdyjski</string>
<string name="lao">Lao</string>
<string name="oriya_macrolanguage">Oriya</string>
<string name="malay_macrolanguage">Malajski</string>
<string name="nauru">Nauru</string>
<string name="navajo">Navaho</string>
<string name="mongolian">Mongolski</string>
<string name="burmese">Birmański</string>
<string name="dutch">Holenderski</string>
<string name="norwegian_bokmål">Norweski Bokmål</string>
<string name="occitan">Occitan</string>
<string name="oromo">Oromo</string>
<string name="ojibwa">Ojibwa</string>
<string name="ossetian">Ossetian</string>
<string name="pakistan_sign_language">Język migowy Pakistanu</string>
<string name="pushto">Pachto</string>
<string name="romansh">Romański</string>
<string name="russian_sign_language">Rosyjski język migowy</string>
<string name="russian">Rosyjski</string>
<string name="slovenian">Słoweński</string>
<string name="sindhi">Sindhi</string>
<string name="quechua">Quechua</string>
<string name="sinhala">Sinhala</string>
<string name="slovak">Słowacki</string>
<string name="shona">Shona</string>
<string name="somali">Somalijski</string>
<string name="swati">Swati</string>
<string name="tagalog">Tagalog</string>
<string name="swahili_macrolanguage">Swahili</string>
<string name="swedish">Szwedzki</string>
<string name="swedish_sign_language">Szwedzki język migowy</string>
<string name="tajik">Tajik</string>
<string name="klingon">Klingon</string>
<string name="tswana">Tswana</string>
<string name="xhosa">Xhosa</string>
<string name="yiddish">Yiddish</string>
<string name="bysa">Uznanie autorstwa - Na tych samych zasadach</string>
<string name="by">Uznanie autorstwa</string>
<string name="byncsa">Uznanie autorstwa - Użycie niekomercyjne - Na tych samych warunkach</string>
<string name="tonga_tonga_islands">Tonga</string>
<string name="twi">Twi</string>
<string name="ukrainian">Ukraiński</string>
<string name="walloon">Walloon</string>
<string name="turkish">Turecki</string>
<string name="uighur">Ujgurski</string>
<string name="urdu">Urdu</string>
<string name="uzbek">Uzbecki</string>
<string name="venda">Venda</string>
<string name="vietnamese">Wietnamski</string>
<string name="wolof">Wolof</string>
<string name="zulu">Zulu</string>
<string name="save_replay_info">Jeśli włączysz tę opcję, twoja transmisja zostanie przerwana jeśli przekroczysz limit miejsca na filmy</string>
</resources>

View File

@ -0,0 +1,281 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">PeerTube Live</string>
<string name="account_exist">Цей обліковий запис уже існує</string>
<string name="network_error">Відсутнє інтернет-з\'єднання</string>
<string name="unknwon_error">Невідома помилка</string>
<string name="unknown_host">Невідомий хост</string>
<string name="json_error">Помилка JSON</string>
<string name="stream_title_error">Назва не повинна бути порожньою</string>
<string name="username_error">Ім\'я користувача не може бути порожнім</string>
<string name="instance_error">Сервер не може бути порожнім</string>
<string name="password_error">Пароль не може бути порожнім</string>
<string name="no_instance">Немає зареєстрованого облікового запису. Додайте обліковий запис PeerTube за допомогою кнопки «+» у верхній панелі</string>
<string name="loading_channels">Завантаження списку каналів</string>
<string name="delete_account">Видалити обліковий запис %s, пов\'язаний із сервером %s\?</string>
<string name="tags_rules">Максимум 5 міток, кожна довжиною від 2 до 30 символів, відокремлених комами</string>
<string name="save_replay_info">Якщо ввімкнете цю опцію, трансляція буде припинена, якщо ви перевищите квоту відео</string>
<string name="background_reason">Пряма трансляція завершилася, оскільки застосунок переведено у фоновий режим</string>
<string name="ask_end_stream">Бажаєте зупинити трансляцію\?</string>
<string name="network_reason">Пряма трансляція завершилася через проблему мережі</string>
<string name="malformed_instance_error">Сервер повинен мати дійсну URL-адресу</string>
<string name="go_live">Почати пряму трансляцію</string>
<string name="yes">Так</string>
<string name="stream_title">Назва</string>
<string name="advanced_settings">▶ Розширені налаштування</string>
<string name="advanced_settings_expand">▼ Розширені налаштування</string>
<string name="add_instance">Додати цей обліковий запис</string>
<string name="connection">З\'єднання</string>
<string name="username">Ім’я користувача</string>
<string name="password">Пароль</string>
<string name="delete_account_title">Видалити цей обліковий запис</string>
<string name="comments_enabled">Увімкнути коментарі до відео</string>
<string name="nsfw">Містить делікатний вміст</string>
<string name="tags">Мітки</string>
<string name="description">Опис</string>
<string name="save_replay">Автопублікування повтору після завершення трансляції</string>
<string name="stream_ended">Пряма трансляція завершилася</string>
<string name="end_stream">Зупинити пряму трансляцію</string>
<string name="creating">Дочекайтеся створення трансляції</string>
<string name="stream_resolution">Роздільність трансляції</string>
<string name="exemple_instance">наприклад, peertube.fr, https://peertube.fr</string>
<string name="music">Музика</string>
<string name="films">Фільми</string>
<string name="vehicles">Транспорт</string>
<string name="art">Мистецтво</string>
<string name="sports">Спорт</string>
<string name="travels">Подорожі</string>
<string name="entertainment">Розваги</string>
<string name="news_politics">Новини й політика</string>
<string name="activism">Активізм</string>
<string name="science_tech">Наука й технології</string>
<string name="animals">Тварини</string>
<string name="kids">Діти</string>
<string name="food">Їжа</string>
<string name="afar">Афарська</string>
<string name="abkhazian">Абхазька</string>
<string name="afrikaans">Африкаанс</string>
<string name="akan">Акан</string>
<string name="amharic">Амхарська</string>
<string name="arabic">Арабська</string>
<string name="instance">Сервер</string>
<string name="live_disabled">Цей сервер вимкнув прямі трансляції.</string>
<string name="aragonese">Арагонська</string>
<string name="american_sign_language">Американська жестова мова</string>
<string name="kotava">Котава</string>
<string name="aymara">Аймарська</string>
<string name="azerbaijani">Азербайджанська</string>
<string name="bashkir">Башкирська</string>
<string name="bambara">Бамбара</string>
<string name="belarusian">Білоруська</string>
<string name="british_sign_language">Британська жестова мова</string>
<string name="bislama">Біслама</string>
<string name="tibetan">Тибетська</string>
<string name="bosnian">Боснійська</string>
<string name="breton">Бретонська</string>
<string name="catalan">Каталонська</string>
<string name="czech">Чеська</string>
<string name="chamorro">Чаморро</string>
<string name="chechen">Чеченська</string>
<string name="chuvash">Чуваська</string>
<string name="cornish">Корнська</string>
<string name="corsican">Корсиканська</string>
<string name="cree">Крі</string>
<string name="czech_sign_language">Чеська жестова мова</string>
<string name="chinese_sign_language">Китайська жестова мова</string>
<string name="welsh">Валлійська</string>
<string name="danish">Данська</string>
<string name="german">Німецька</string>
<string name="dhivehi">Мальдівська</string>
<string name="danish_sign_language">Данська жестова мова</string>
<string name="dzongkha">Дзонґ-ке</string>
<string name="greek">Грецька</string>
<string name="esperanto">Есперанто</string>
<string name="ewe">Еве</string>
<string name="faroese">Фарерська</string>
<string name="persian">Перська</string>
<string name="fijian">Фіджійська</string>
<string name="finnish">Фінська</string>
<string name="french">Французька</string>
<string name="french_sign_language">Французька жестова мова</string>
<string name="fulah">Фульфульде</string>
<string name="scottish_gaelic">Шотландська гельська</string>
<string name="irish">Ірландська</string>
<string name="manx">Менська</string>
<string name="german_sign_language">Німецька жестова мова</string>
<string name="hausa">Гауса</string>
<string name="serbo_croatian">Сербохорватська</string>
<string name="herero">Гереро</string>
<string name="hindi">Гінді</string>
<string name="hiri_motu">Гірі-моту</string>
<string name="croatian">Хорватська</string>
<string name="armenian">Вірменська</string>
<string name="igbo">Ігбо</string>
<string name="sichuan_yi">Носу</string>
<string name="inuktitut">Інуктитут</string>
<string name="inupiaq">Інупіак</string>
<string name="icelandic">Ісландська</string>
<string name="italian">Італійська</string>
<string name="javanese">Яванська</string>
<string name="lojban">Ложбан</string>
<string name="japanese">Японська</string>
<string name="japanese_sign_language">Японська жестова мова</string>
<string name="kabyle">Кабільська</string>
<string name="kalaallisut">Гренландська</string>
<string name="kannada">Каннада</string>
<string name="kashmiri">Кашмірська</string>
<string name="georgian">Грузинська</string>
<string name="kanuri">Канурі</string>
<string name="kazakh">Казахська</string>
<string name="khmer">Кхмерська</string>
<string name="kikuyu">Кікую</string>
<string name="kinyarwanda">Руандійська</string>
<string name="kirghiz">Киргизька</string>
<string name="komi">Комі</string>
<string name="kongo">Конґо</string>
<string name="korean">Корейська</string>
<string name="kuanyama">Кваньяма</string>
<string name="kurdish">Курдська</string>
<string name="lao">Лаоська</string>
<string name="latvian">Латиська</string>
<string name="limburgan">Лімбурзька</string>
<string name="lingala">Лінґала</string>
<string name="lithuanian">Литовська</string>
<string name="luba_katanga">Луба-Катанґа</string>
<string name="ganda">Гандійська</string>
<string name="marshallese">Маршальська</string>
<string name="malayalam">Малаялам</string>
<string name="macedonian">Македонська</string>
<string name="malagasy">Малагасійська</string>
<string name="maltese">Мальтійська</string>
<string name="maori">Маорійська</string>
<string name="malay_macrolanguage">Малайська (макромова)</string>
<string name="burmese">Бірманська</string>
<string name="nauru">Науру</string>
<string name="navajo">Навахо</string>
<string name="north_ndebele">Північна Ндебеле</string>
<string name="ndonga">Ндонга</string>
<string name="nepali_macrolanguage">Непальська (макромова)</string>
<string name="norwegian_nynorsk">Норвезька Нюношк</string>
<string name="norwegian_bokmål">Норвезька Букмол</string>
<string name="nyanja">Ньянджа</string>
<string name="occitan">Окситанська</string>
<string name="ojibwa">Оджибвемовінська</string>
<string name="oriya_macrolanguage">Орія (макромова)</string>
<string name="oromo">Оромо</string>
<string name="ossetian">Осетинська</string>
<string name="panjabi">Панджабі</string>
<string name="pakistan_sign_language">Пакистанська жестова мова</string>
<string name="pushto">Пушту</string>
<string name="quechua">Кечуа</string>
<string name="romansh">Ретороманська</string>
<string name="romanian">Румунська</string>
<string name="rundi">Рунді</string>
<string name="russian">Російська</string>
<string name="sango">Санґо</string>
<string name="saudi_arabian_sign_language">Жестова мова Саудівської Аравії</string>
<string name="south_african_sign_language">Південноафриканська жестова мова</string>
<string name="sinhala">Сингальська</string>
<string name="slovak">Словацька</string>
<string name="northern_sami">Північносаамська</string>
<string name="samoan">Самоанська</string>
<string name="shona">Шона</string>
<string name="sindhi">Синдхі</string>
<string name="somali">Сомалійська</string>
<string name="southern_sotho">Південне Сото</string>
<string name="sardinian">Сардинська</string>
<string name="serbian">Сербська</string>
<string name="swati">Сваті</string>
<string name="sundanese">Сунданська</string>
<string name="swahili_macrolanguage">Суахілі (макромова)</string>
<string name="telugu">Телугу</string>
<string name="tajik">Таджицька</string>
<string name="tagalog">Тагальська</string>
<string name="thai">Тайська</string>
<string name="tigrinya">Тигринья</string>
<string name="klingon">Клінгонська</string>
<string name="tonga_tonga_islands">Тонга (Острови Тонга)</string>
<string name="tsonga">Тсонга</string>
<string name="turkmen">Туркменська</string>
<string name="twi">Тві</string>
<string name="uighur">Уйгурська</string>
<string name="ukrainian">Українська</string>
<string name="urdu">Урду</string>
<string name="uzbek">Узбецька</string>
<string name="venda">Венда</string>
<string name="vietnamese">В\'єтнамська</string>
<string name="walloon">Валлонська</string>
<string name="wolof">Волоф</string>
<string name="xhosa">Коса</string>
<string name="yiddish">Їдиш</string>
<string name="yoruba">Йоруба</string>
<string name="zhuang">Чжуанська</string>
<string name="chinese">Китайська</string>
<string name="zulu">Зулуська</string>
<string name="by">Атрибуція</string>
<string name="bysa">Атрибуція - Спільний доступ</string>
<string name="bynd">Атрибуція - Без похідних</string>
<string name="bync">Атрибуція - Некомерційна</string>
<string name="byncsa">Атрибуція - Некомерційна - На тих же умовах</string>
<string name="byncnd">Атрибуція - Некомерційна - Без похідних</string>
<string name="public_domain">Призначено для громадського надбання</string>
<string name="privacy_public">Загальнодоступний</string>
<string name="unlisted">Не входить до списку</string>
<string name="goto_permissions">Переглянути налаштування</string>
<string name="back_reason">Пряму трансляцію завершено натисканням кнопки «Назад»</string>
<string name="lock_reason">Пряма трансляція завершилася, оскільки телефон заблоковано</string>
<string name="try_connect">Очікування з\'єднання</string>
<string name="permissions">Для прямої трансляції потрібен доступ до камери та мікрофона. Натисніть унизу, щоб змінити налаштування дозволів.</string>
<string name="stop_reason">Ваша пряма трансляція завершилася</string>
<string name="choose_channel">Канал</string>
<string name="no">Ні</string>
<string name="cancel">Скасувати</string>
<string name="connect">Під\'єднатися</string>
<string name="stream_category">Категорія</string>
<string name="stream_privacy">Приватність</string>
<string name="stream_language">Мова</string>
<string name="download_enabled">Увімкнути завантаження</string>
<string name="stream_licence">Ліцензія</string>
<string name="gaming">Ігри</string>
<string name="comedy">Комедія</string>
<string name="how_to">Поради</string>
<string name="education">Освіта</string>
<string name="people">Люди</string>
<string name="assamese">Ассамська</string>
<string name="avaric">Аварська</string>
<string name="bulgarian">Болгарська</string>
<string name="brazilian_sign_language">Бразильська жестова мова</string>
<string name="bengali">Бенгальська</string>
<string name="swedish">Шведська</string>
<string name="swedish_sign_language">Шведська жестова мова</string>
<string name="tamil">Тамільська</string>
<string name="tatar">Татарська</string>
<string name="tahitian">Таїтянська</string>
<string name="tswana">Тсвана</string>
<string name="estonian">Естонська</string>
<string name="guarani">Гуарані</string>
<string name="gujarati">Гуджараті</string>
<string name="galician">Галісійська</string>
<string name="english">Англійська</string>
<string name="basque">Баскська</string>
<string name="western_frisian">Західнофризька</string>
<string name="mongolian">Монгольська</string>
<string name="albanian">Албанська</string>
<string name="hebrew">Іврит</string>
<string name="haitian">Гаїтянська</string>
<string name="hungarian">Угорська</string>
<string name="indonesian">Індонезійська</string>
<string name="luxembourgish">Люксембурзька</string>
<string name="marathi">Маратхі</string>
<string name="russian_sign_language">Російська жестова мова</string>
<string name="south_ndebele">Південна Ндебеле</string>
<string name="dutch">Нідерландська</string>
<string name="norwegian">Норвезька</string>
<string name="polish">Польська</string>
<string name="portuguese">Португальська</string>
<string name="spanish">іспанська</string>
<string name="slovenian">Словенська</string>
<string name="turkish">Турецька</string>
<string name="privacy_private">Приватний</string>
<string name="internal">Внутрішній</string>
</resources>

View File

@ -10,6 +10,8 @@
<string name="instance_error">Instance cannot be empty</string>
<string name="username_error">Username cannot be empty</string>
<string name="password_error">Password cannot be empty</string>
<string name="two_fa_error">Two-factor authentication token cannot be empty</string>
<string name="invalid_two_fa_token">Two-factor authentication token is not valid</string>
<string name="malformed_instance_error">The instance must have a valid URL</string>
<string name="account_exist">This account already exist</string>
<!-- messages -->
@ -48,6 +50,7 @@
<string name="username">Username</string>
<string name="password">Password</string>
<string name="instance">Instance</string>
<string name="two_fa">Two-factor Authentication Token</string>
<string name="delete_account_title">Delete this account</string>
<string name="comments_enabled">Enable video comments</string>
<string name="download_enabled">Enable download</string>

View File

@ -1,14 +1,16 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.5.10'
ext.kotlin_version = '1.9.22'
ext.rtsp_version_name = '2.1.9'
ext.rtsp_version_code = 219
repositories {
google()
jcenter()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.0.2'
classpath 'com.android.tools.build:gradle:8.3.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5'
classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.7.7'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@ -18,11 +20,11 @@ buildscript {
allprojects {
repositories {
google()
jcenter()
mavenCentral()
maven { url 'https://jitpack.io' }
}
}
task clean(type: Delete) {
tasks.register('clean', Delete) {
delete rootProject.buildDir
}

View File

@ -1,11 +1,13 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'maven-publish'
android {
compileSdkVersion 30
compileSdkVersion 34
defaultConfig {
minSdkVersion 16
targetSdkVersion 30
targetSdkVersion 34
}
buildTypes {
release {
@ -13,8 +15,15 @@ android {
consumerProguardFiles 'proguard-rules.pro'
}
}
namespace 'com.pedro.encoder'
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
}
dependencies {
api 'androidx.annotation:annotation:1.2.0'
api 'androidx.annotation:annotation:1.8.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
}

View File

@ -1 +1 @@
<manifest package="com.pedro.encoder" />
<manifest />

View File

@ -1,37 +1,145 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import com.pedro.encoder.utils.CodecUtil;
import java.nio.ByteBuffer;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
/**
* Created by pedro on 18/09/19.
*/
public abstract class BaseEncoder implements EncoderCallback {
private static final String TAG = "BaseEncoder";
private MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
protected String TAG = "BaseEncoder";
private final MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
private HandlerThread handlerThread;
protected BlockingQueue<Frame> queue = new ArrayBlockingQueue<>(80);
protected MediaCodec codec;
protected long presentTimeUs;
protected static long presentTimeUs;
protected volatile boolean running = false;
protected boolean isBufferMode = true;
protected CodecUtil.Force force = CodecUtil.Force.FIRST_COMPATIBLE_FOUND;
private MediaCodec.Callback callback;
private long oldTimeStamp = 0L;
protected boolean shouldReset = true;
public void restart() {
start(false);
initCodec();
}
public void start() {
if (presentTimeUs == 0) {
presentTimeUs = System.nanoTime() / 1000;
}
start(true);
initCodec();
}
private void initCodec() {
handlerThread = new HandlerThread(TAG);
handlerThread.start();
Handler handler = new Handler(handlerThread.getLooper());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
createAsyncCallback();
codec.setCallback(callback, handler);
codec.start();
} else {
codec.start();
handler.post(new Runnable() {
@Override
public void run() {
while (running) {
try {
getDataFromEncoder();
} catch (IllegalStateException e) {
Log.i(TAG, "Encoding error", e);
reloadCodec();
}
}
}
});
}
running = true;
}
public abstract void reset();
public abstract void start(boolean resetTs);
protected abstract void stopImp();
protected void fixTimeStamp(MediaCodec.BufferInfo info) {
if (oldTimeStamp > info.presentationTimeUs) {
info.presentationTimeUs = oldTimeStamp;
} else {
oldTimeStamp = info.presentationTimeUs;
}
}
private void reloadCodec() {
//Sometimes encoder crash, we will try recover it. Reset encoder a time if crash
if (shouldReset) {
Log.e(TAG, "Encoder crashed, trying to recover it");
reset();
}
}
public void stop() {
stop(true);
}
public void stop(boolean resetTs) {
if (resetTs) {
presentTimeUs = 0;
}
running = false;
stopImp();
if (handlerThread != null) {
if (handlerThread.getLooper() != null) {
if (handlerThread.getLooper().getThread() != null) {
handlerThread.getLooper().getThread().interrupt();
}
handlerThread.getLooper().quit();
}
handlerThread.quit();
if (codec != null) {
try {
codec.flush();
} catch (IllegalStateException ignored) { }
}
//wait for thread to die for 500ms.
try {
handlerThread.getLooper().getThread().join(500);
} catch (Exception ignored) { }
}
queue.clear();
queue = new ArrayBlockingQueue<>(80);
try {
codec.stop();
codec.release();
@ -39,18 +147,19 @@ public abstract class BaseEncoder implements EncoderCallback {
} catch (IllegalStateException | NullPointerException e) {
codec = null;
}
oldTimeStamp = 0L;
}
protected abstract MediaCodecInfo chooseEncoder(String mime);
protected void getDataFromEncoder(Frame frame) throws IllegalStateException {
protected void getDataFromEncoder() throws IllegalStateException {
if (isBufferMode) {
int inBufferIndex = codec.dequeueInputBuffer(0);
if (inBufferIndex >= 0) {
inputAvailable(codec, inBufferIndex, frame);
inputAvailable(codec, inBufferIndex);
}
}
for (; running; ) {
while (running) {
int outBufferIndex = codec.dequeueOutputBuffer(bufferInfo, 0);
if (outBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
MediaFormat mediaFormat = codec.getOutputFormat();
@ -65,16 +174,22 @@ public abstract class BaseEncoder implements EncoderCallback {
protected abstract Frame getInputFrame() throws InterruptedException;
protected abstract long calculatePts(Frame frame, long presentTimeUs);
private void processInput(@NonNull ByteBuffer byteBuffer, @NonNull MediaCodec mediaCodec,
int inBufferIndex, Frame frame) throws IllegalStateException {
int inBufferIndex) throws IllegalStateException {
try {
if (frame == null) frame = getInputFrame();
Frame frame = getInputFrame();
while (frame == null) frame = getInputFrame();
byteBuffer.clear();
byteBuffer.put(frame.getBuffer(), frame.getOffset(), frame.getSize());
long pts = System.nanoTime() / 1000 - presentTimeUs;
mediaCodec.queueInputBuffer(inBufferIndex, 0, frame.getSize(), pts, 0);
int size = Math.max(0, Math.min(frame.getSize(), byteBuffer.remaining()) - frame.getOffset());
byteBuffer.put(frame.getBuffer(), frame.getOffset(), size);
long pts = calculatePts(frame, presentTimeUs);
mediaCodec.queueInputBuffer(inBufferIndex, 0, size, pts, 0);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (NullPointerException | IndexOutOfBoundsException e) {
Log.i(TAG, "Encoding error", e);
}
}
@ -100,7 +215,7 @@ public abstract class BaseEncoder implements EncoderCallback {
}
@Override
public void inputAvailable(@NonNull MediaCodec mediaCodec, int inBufferIndex, Frame frame)
public void inputAvailable(@NonNull MediaCodec mediaCodec, int inBufferIndex)
throws IllegalStateException {
ByteBuffer byteBuffer;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
@ -108,7 +223,7 @@ public abstract class BaseEncoder implements EncoderCallback {
} else {
byteBuffer = mediaCodec.getInputBuffers()[inBufferIndex];
}
processInput(byteBuffer, mediaCodec, inBufferIndex, frame);
processInput(byteBuffer, mediaCodec, inBufferIndex);
}
@Override
@ -122,4 +237,41 @@ public abstract class BaseEncoder implements EncoderCallback {
}
processOutput(byteBuffer, mediaCodec, outBufferIndex, bufferInfo);
}
@RequiresApi(api = Build.VERSION_CODES.M)
private void createAsyncCallback() {
callback = new MediaCodec.Callback() {
@Override
public void onInputBufferAvailable(@NonNull MediaCodec mediaCodec, int inBufferIndex) {
try {
inputAvailable(mediaCodec, inBufferIndex);
} catch (IllegalStateException e) {
Log.i(TAG, "Encoding error", e);
reloadCodec();
}
}
@Override
public void onOutputBufferAvailable(@NonNull MediaCodec mediaCodec, int outBufferIndex,
@NonNull MediaCodec.BufferInfo bufferInfo) {
try {
outputAvailable(mediaCodec, outBufferIndex, bufferInfo);
} catch (IllegalStateException e) {
Log.i(TAG, "Encoding error", e);
reloadCodec();
}
}
@Override
public void onError(@NonNull MediaCodec mediaCodec, @NonNull MediaCodec.CodecException e) {
Log.e(TAG, "Error", e);
}
@Override
public void onOutputFormatChanged(@NonNull MediaCodec mediaCodec,
@NonNull MediaFormat mediaFormat) {
formatChanged(mediaCodec, mediaFormat);
}
};
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder;
import android.media.MediaCodec;
@ -8,7 +24,7 @@ import androidx.annotation.NonNull;
* Created by pedro on 18/09/19.
*/
public interface EncoderCallback {
void inputAvailable(@NonNull MediaCodec mediaCodec, int inBufferIndex, Frame frame)
void inputAvailable(@NonNull MediaCodec mediaCodec, int inBufferIndex)
throws IllegalStateException;
void outputAvailable(@NonNull MediaCodec mediaCodec, int outBufferIndex,

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder;
import android.graphics.ImageFormat;

View File

@ -0,0 +1,21 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder;
public interface GetFrame {
Frame getInputFrame();
}

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.audio;
import android.media.MediaCodec;
@ -7,11 +23,10 @@ import android.util.Log;
import androidx.annotation.NonNull;
import com.pedro.encoder.BaseEncoder;
import com.pedro.encoder.Frame;
import com.pedro.encoder.GetFrame;
import com.pedro.encoder.input.audio.GetMicrophoneData;
import com.pedro.encoder.utils.CodecUtil;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
/**
@ -22,15 +37,18 @@ import java.util.List;
public class AudioEncoder extends BaseEncoder implements GetMicrophoneData {
private static final String TAG = "AudioEncoder";
private GetAacData getAacData;
private final GetAacData getAacData;
private int bitRate = 64 * 1024; //in kbps
private int sampleRate = 32000; //in hz
private int maxInputSize = 0;
private boolean isStereo = true;
private GetFrame getFrame;
private long bytesRead = 0;
private boolean tsModeBuffer = false;
public AudioEncoder(GetAacData getAacData) {
this.getAacData = getAacData;
TAG = "AudioEncoder";
}
/**
@ -38,31 +56,19 @@ public class AudioEncoder extends BaseEncoder implements GetMicrophoneData {
*/
public boolean prepareAudioEncoder(int bitRate, int sampleRate, boolean isStereo,
int maxInputSize) {
this.bitRate = bitRate;
this.sampleRate = sampleRate;
this.maxInputSize = maxInputSize;
this.isStereo = isStereo;
isBufferMode = true;
try {
List<MediaCodecInfo> encoders = new ArrayList<>();
if (force == CodecUtil.Force.HARDWARE) {
encoders = CodecUtil.getAllHardwareEncoders(CodecUtil.AAC_MIME);
} else if (force == CodecUtil.Force.SOFTWARE) {
encoders = CodecUtil.getAllSoftwareEncoders(CodecUtil.AAC_MIME);
}
if (force == CodecUtil.Force.FIRST_COMPATIBLE_FOUND) {
MediaCodecInfo encoder = chooseEncoder(CodecUtil.AAC_MIME);
if (encoder != null) {
codec = MediaCodec.createByCodecName(encoder.getName());
} else {
Log.e(TAG, "Valid encoder not found");
return false;
}
MediaCodecInfo encoder = chooseEncoder(CodecUtil.AAC_MIME);
if (encoder != null) {
Log.i(TAG, "Encoder selected " + encoder.getName());
codec = MediaCodec.createByCodecName(encoder.getName());
} else {
if (encoders.isEmpty()) {
Log.e(TAG, "Valid encoder not found");
return false;
} else {
codec = MediaCodec.createByCodecName(encoders.get(0).getName());
}
Log.e(TAG, "Valid encoder not found");
return false;
}
int channelCount = (isStereo) ? 2 : 1;
@ -76,41 +82,65 @@ public class AudioEncoder extends BaseEncoder implements GetMicrophoneData {
running = false;
Log.i(TAG, "prepared");
return true;
} catch (IOException | IllegalStateException e) {
} catch (Exception e) {
Log.e(TAG, "Create AudioEncoder failed.", e);
this.stop();
return false;
}
}
public void setGetFrame(GetFrame getFrame) {
this.getFrame = getFrame;
}
/**
* Prepare encoder with default parameters
*/
public boolean prepareAudioEncoder() {
return prepareAudioEncoder(bitRate, sampleRate, isStereo, 0);
return prepareAudioEncoder(bitRate, sampleRate, isStereo, maxInputSize);
}
@Override
public void start(boolean resetTs) {
presentTimeUs = System.nanoTime() / 1000;
codec.start();
running = true;
shouldReset = resetTs;
Log.i(TAG, "started");
}
@Override
protected void stopImp() {
bytesRead = 0;
Log.i(TAG, "stopped");
}
@Override
public void reset() {
stop(false);
prepareAudioEncoder(bitRate, sampleRate, isStereo, maxInputSize);
restart();
}
@Override
protected Frame getInputFrame() throws InterruptedException {
return null;
return getFrame != null ? getFrame.getInputFrame() : queue.take();
}
@Override
protected long calculatePts(Frame frame, long presentTimeUs) {
long pts;
if (tsModeBuffer) {
int channels = isStereo ? 2 : 1;
pts = 1000000 * bytesRead / 2 / channels / sampleRate;
bytesRead += frame.getSize();
} else {
pts = System.nanoTime() / 1000 - presentTimeUs;
}
return pts;
}
@Override
protected void checkBuffer(@NonNull ByteBuffer byteBuffer,
@NonNull MediaCodec.BufferInfo bufferInfo) {
fixTimeStamp(bufferInfo);
}
@Override
@ -123,39 +153,45 @@ public class AudioEncoder extends BaseEncoder implements GetMicrophoneData {
* Set custom PCM data.
* Use it after prepareAudioEncoder(int sampleRate, int channel).
* Used too with microphone.
*
*/
@Override
public void inputPCMData(Frame frame) {
if (running) {
try {
getDataFromEncoder(frame);
} catch (IllegalStateException e) {
Log.i(TAG, "Encoding error", e);
}
} else {
if (running && !queue.offer(frame)) {
Log.i(TAG, "frame discarded");
}
}
@Override
protected MediaCodecInfo chooseEncoder(String mime) {
List<MediaCodecInfo> mediaCodecInfoList = CodecUtil.getAllEncoders(mime);
for (MediaCodecInfo mediaCodecInfo : mediaCodecInfoList) {
String name = mediaCodecInfo.getName().toLowerCase();
if (!name.contains("omx.google")) return mediaCodecInfo;
}
if (mediaCodecInfoList.size() > 0) {
return mediaCodecInfoList.get(0);
List<MediaCodecInfo> mediaCodecInfoList;
if (force == CodecUtil.Force.HARDWARE) {
mediaCodecInfoList = CodecUtil.getAllHardwareEncoders(CodecUtil.AAC_MIME);
} else if (force == CodecUtil.Force.SOFTWARE) {
mediaCodecInfoList = CodecUtil.getAllSoftwareEncoders(CodecUtil.AAC_MIME);
} else {
return null;
//Priority: hardware > software
mediaCodecInfoList = CodecUtil.getAllEncoders(CodecUtil.AAC_MIME, true);
}
Log.i(TAG, mediaCodecInfoList.size() + " encoders found");
if (mediaCodecInfoList.isEmpty()) return null;
else return mediaCodecInfoList.get(0);
}
public void setSampleRate(int sampleRate) {
this.sampleRate = sampleRate;
}
public boolean isTsModeBuffer() {
return tsModeBuffer;
}
public void setTsModeBuffer(boolean tsModeBuffer) {
if (!isRunning()) {
this.tsModeBuffer = tsModeBuffer;
}
}
@Override
public void formatChanged(@NonNull MediaCodec mediaCodec, @NonNull MediaFormat mediaFormat) {
getAacData.onAudioFormat(mediaFormat);

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.audio;
import android.media.MediaCodec;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.audio;
import android.media.audiofx.AcousticEchoCanceler;
@ -13,7 +29,7 @@ public class AudioPostProcessEffect {
private final String TAG = "AudioPostProcessEffect";
private int microphoneId;
private final int microphoneId;
private AcousticEchoCanceler acousticEchoCanceler = null;
private AutomaticGainControl automaticGainControl = null;
private NoiseSuppressor noiseSuppressor = null;
@ -25,11 +41,13 @@ public class AudioPostProcessEffect {
public void enableAutoGainControl() {
if (AutomaticGainControl.isAvailable() && automaticGainControl == null) {
automaticGainControl = AutomaticGainControl.create(microphoneId);
automaticGainControl.setEnabled(true);
Log.i(TAG, "AutoGainControl enabled");
} else {
Log.e(TAG, "This device don't support AutoGainControl");
if (automaticGainControl != null) {
automaticGainControl.setEnabled(true);
Log.i(TAG, "AutoGainControl enabled");
return;
}
}
Log.e(TAG, "This device doesn't implement AutoGainControl");
}
public void releaseAutoGainControl() {
@ -43,11 +61,13 @@ public class AudioPostProcessEffect {
public void enableEchoCanceler() {
if (AcousticEchoCanceler.isAvailable() && acousticEchoCanceler == null) {
acousticEchoCanceler = AcousticEchoCanceler.create(microphoneId);
acousticEchoCanceler.setEnabled(true);
Log.i(TAG, "EchoCanceler enabled");
} else {
Log.e(TAG, "This device don't support EchoCanceler");
if (acousticEchoCanceler != null) {
acousticEchoCanceler.setEnabled(true);
Log.i(TAG, "EchoCanceler enabled");
return;
}
}
Log.e(TAG, "This device doesn't implement EchoCanceler");
}
public void releaseEchoCanceler() {
@ -61,11 +81,13 @@ public class AudioPostProcessEffect {
public void enableNoiseSuppressor() {
if (NoiseSuppressor.isAvailable() && noiseSuppressor == null) {
noiseSuppressor = NoiseSuppressor.create(microphoneId);
noiseSuppressor.setEnabled(true);
Log.i(TAG, "NoiseSuppressor enabled");
} else {
Log.e(TAG, "This device don't support NoiseSuppressor");
if (noiseSuppressor != null) {
noiseSuppressor.setEnabled(true);
Log.i(TAG, "NoiseSuppressor enabled");
return;
}
}
Log.e(TAG, "This device doesn't implement NoiseSuppressor");
}
public void releaseNoiseSuppressor() {
@ -75,4 +97,10 @@ public class AudioPostProcessEffect {
noiseSuppressor = null;
}
}
public void release() {
releaseAutoGainControl();
releaseEchoCanceler();
releaseNoiseSuppressor();
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.audio;
public abstract class CustomAudioEffect {

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.audio;
import com.pedro.encoder.Frame;

View File

@ -1,5 +1,22 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.audio;
import android.annotation.SuppressLint;
import android.media.AudioFormat;
import android.media.AudioPlaybackCaptureConfiguration;
import android.media.AudioRecord;
@ -16,28 +33,29 @@ import java.nio.ByteBuffer;
* Created by pedro on 19/01/17.
*/
@SuppressLint("MissingPermission")
public class MicrophoneManager {
private final String TAG = "MicrophoneManager";
private static final int BUFFER_SIZE = 4096;
private int BUFFER_SIZE = 0;
protected AudioRecord audioRecord;
private GetMicrophoneData getMicrophoneData;
private ByteBuffer pcmBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
private byte[] pcmBufferMuted = new byte[BUFFER_SIZE];
private final GetMicrophoneData getMicrophoneData;
protected byte[] pcmBuffer = new byte[BUFFER_SIZE];
protected byte[] pcmBufferMuted = new byte[BUFFER_SIZE];
protected boolean running = false;
private boolean created = false;
//default parameters for microphone
private int sampleRate = 32000; //hz
private int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
private final int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
private int channel = AudioFormat.CHANNEL_IN_STEREO;
private boolean muted = false;
protected boolean muted = false;
private AudioPostProcessEffect audioPostProcessEffect;
HandlerThread handlerThread;
private CustomAudioEffect customAudioEffect = new NoAudioEffect();
protected HandlerThread handlerThread;
protected CustomAudioEffect customAudioEffect = new NoAudioEffect();
public MicrophoneManager(GetMicrophoneData getMicrophoneData) {
this.getMicrophoneData = getMicrophoneData;
getPcmBufferSize();
}
public void setCustomAudioEffect(CustomAudioEffect customAudioEffect) {
@ -55,61 +73,86 @@ public class MicrophoneManager {
/**
* Create audio record with params and default audio source
*/
public void createMicrophone(int sampleRate, boolean isStereo, boolean echoCanceler,
public boolean createMicrophone(int sampleRate, boolean isStereo, boolean echoCanceler,
boolean noiseSuppressor) {
createMicrophone(MediaRecorder.AudioSource.DEFAULT, sampleRate, isStereo, echoCanceler, noiseSuppressor);
return createMicrophone(MediaRecorder.AudioSource.DEFAULT, sampleRate, isStereo, echoCanceler,
noiseSuppressor);
}
/**
* Create audio record with params and selected audio source
* @param audioSource - the recording source. See {@link MediaRecorder.AudioSource} for the recording source definitions.
*
* @param audioSource - the recording source. See {@link MediaRecorder.AudioSource} for the
* recording source definitions.
*/
public void createMicrophone(int audioSource, int sampleRate, boolean isStereo, boolean echoCanceler,
boolean noiseSuppressor) {
this.sampleRate = sampleRate;
if (!isStereo) channel = AudioFormat.CHANNEL_IN_MONO;
audioRecord =
new AudioRecord(audioSource, sampleRate, channel, audioFormat,
getPcmBufferSize());
audioPostProcessEffect = new AudioPostProcessEffect(audioRecord.getAudioSessionId());
if (echoCanceler) audioPostProcessEffect.enableEchoCanceler();
if (noiseSuppressor) audioPostProcessEffect.enableNoiseSuppressor();
String chl = (isStereo) ? "Stereo" : "Mono";
Log.i(TAG, "Microphone created, " + sampleRate + "hz, " + chl);
created = true;
public boolean createMicrophone(int audioSource, int sampleRate, boolean isStereo,
boolean echoCanceler, boolean noiseSuppressor) {
try {
this.sampleRate = sampleRate;
channel = isStereo ? AudioFormat.CHANNEL_IN_STEREO : AudioFormat.CHANNEL_IN_MONO;
audioRecord = new AudioRecord(audioSource, sampleRate, channel, audioFormat, getMaxInputSize() * 5);
audioPostProcessEffect = new AudioPostProcessEffect(audioRecord.getAudioSessionId());
if (echoCanceler) audioPostProcessEffect.enableEchoCanceler();
if (noiseSuppressor) audioPostProcessEffect.enableNoiseSuppressor();
String chl = (isStereo) ? "Stereo" : "Mono";
if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
throw new IllegalArgumentException("Some parameters specified is not valid");
}
Log.i(TAG, "Microphone created, " + sampleRate + "hz, " + chl);
created = true;
} catch (IllegalArgumentException e) {
Log.e(TAG, "create microphone error", e);
}
return created;
}
/**
* Create audio record with params and AudioPlaybackCaptureConfig used for capturing internal audio
* Notice that you should granted {@link android.Manifest.permission#RECORD_AUDIO} before calling this!
*
* @param config - AudioPlaybackCaptureConfiguration received from {@link android.media.projection.MediaProjection}
* Create audio record with params and AudioPlaybackCaptureConfig used for capturing internal
* audio
* Notice that you should granted {@link android.Manifest.permission#RECORD_AUDIO} before calling
* this!
*
* @param config - AudioPlaybackCaptureConfiguration received from {@link
* android.media.projection.MediaProjection}
* @see AudioPlaybackCaptureConfiguration.Builder#Builder(MediaProjection)
* @see "https://developer.android.com/guide/topics/media/playback-capture"
* @see "https://medium.com/@debuggingisfun/android-10-audio-capture-77dd8e9070f9"
*/
public void createInternalMicrophone(AudioPlaybackCaptureConfiguration config, int sampleRate, boolean isStereo) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
this.sampleRate = sampleRate;
if (!isStereo) channel = AudioFormat.CHANNEL_IN_MONO;
audioRecord = new AudioRecord.Builder()
.setAudioPlaybackCaptureConfig(config)
.setAudioFormat(new AudioFormat.Builder()
.setEncoding(audioFormat)
.setSampleRate(sampleRate)
.setChannelMask(channel)
.build())
.setBufferSizeInBytes(getPcmBufferSize())
.build();
audioPostProcessEffect = new AudioPostProcessEffect(audioRecord.getAudioSessionId());
String chl = (isStereo) ? "Stereo" : "Mono";
Log.i(TAG, "Internal microphone created, " + sampleRate + "hz, " + chl);
created = true;
} else createMicrophone(sampleRate, isStereo, false, false);
public boolean createInternalMicrophone(AudioPlaybackCaptureConfiguration config, int sampleRate,
boolean isStereo, boolean echoCanceler, boolean noiseSuppressor) {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
this.sampleRate = sampleRate;
channel = isStereo ? AudioFormat.CHANNEL_IN_STEREO : AudioFormat.CHANNEL_IN_MONO;
audioRecord = new AudioRecord.Builder().setAudioPlaybackCaptureConfig(config)
.setAudioFormat(new AudioFormat.Builder().setEncoding(audioFormat)
.setSampleRate(sampleRate)
.setChannelMask(channel)
.build())
.setBufferSizeInBytes(getMaxInputSize() * 5)
.build();
audioPostProcessEffect = new AudioPostProcessEffect(audioRecord.getAudioSessionId());
if (echoCanceler) audioPostProcessEffect.enableEchoCanceler();
if (noiseSuppressor) audioPostProcessEffect.enableNoiseSuppressor();
String chl = (isStereo) ? "Stereo" : "Mono";
if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
throw new IllegalArgumentException("Some parameters specified is not valid");
}
Log.i(TAG, "Internal microphone created, " + sampleRate + "hz, " + chl);
created = true;
} else {
return createMicrophone(sampleRate, isStereo, echoCanceler, noiseSuppressor);
}
} catch (IllegalArgumentException e) {
Log.e(TAG, "create microphone error", e);
}
return created;
}
public boolean createInternalMicrophone(AudioPlaybackCaptureConfiguration config, int sampleRate,
boolean isStereo) {
return createInternalMicrophone(config, sampleRate, isStereo, false, false);
}
/**
* Start record and get data
@ -126,8 +169,6 @@ public class MicrophoneManager {
Frame frame = read();
if (frame != null) {
getMicrophoneData.inputPCMData(frame);
} else {
running = false;
}
}
}
@ -160,14 +201,10 @@ public class MicrophoneManager {
/**
* @return Object with size and PCM buffer data
*/
private Frame read() {
pcmBuffer.rewind();
int size = audioRecord.read(pcmBuffer, pcmBuffer.remaining());
if (size <= 0) {
return null;
}
return new Frame(muted ? pcmBufferMuted : customAudioEffect.process(pcmBuffer.array()),
muted ? 0 : pcmBuffer.arrayOffset(), size);
protected Frame read() {
int size = audioRecord.read(pcmBuffer, 0, pcmBuffer.length);
if (size < 0) return null;
return new Frame(muted ? pcmBufferMuted : customAudioEffect.process(pcmBuffer), 0, size);
}
/**
@ -190,8 +227,7 @@ public class MicrophoneManager {
audioRecord = null;
}
if (audioPostProcessEffect != null) {
audioPostProcessEffect.releaseEchoCanceler();
audioPostProcessEffect.releaseNoiseSuppressor();
audioPostProcessEffect.release();
}
Log.i(TAG, "Microphone stopped");
}
@ -199,10 +235,10 @@ public class MicrophoneManager {
/**
* Get PCM buffer size
*/
private int getPcmBufferSize() {
int pcmBufSize =
AudioRecord.getMinBufferSize(sampleRate, channel, AudioFormat.ENCODING_PCM_16BIT);
return pcmBufSize * 5;
private void getPcmBufferSize() {
BUFFER_SIZE = AudioRecord.getMinBufferSize(sampleRate, channel, audioFormat);
pcmBuffer = new byte[BUFFER_SIZE];
pcmBufferMuted = new byte[BUFFER_SIZE];
}
public int getMaxInputSize() {

View File

@ -1,15 +1,33 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.audio;
import android.os.HandlerThread;
import android.util.Log;
import com.pedro.encoder.Frame;
import com.pedro.encoder.GetFrame;
import java.nio.ByteBuffer;
/**
* Similar to MicrophoneManager but samples are not read automatically.
* The owner must manually call read(...) as often as samples are needed.
*/
public class MicrophoneManagerManual extends MicrophoneManager {
public class MicrophoneManagerManual extends MicrophoneManager implements GetFrame {
private final String TAG = "MicMM";
@ -54,4 +72,13 @@ public class MicrophoneManagerManual extends MicrophoneManager {
handlerThread = new HandlerThread("nothing");
super.stop();
}
public GetFrame getGetFrame() {
return this;
}
@Override
public Frame getInputFrame() {
return read();
}
}

View File

@ -0,0 +1,21 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.audio;
public enum MicrophoneMode {
SYNC, ASYNC, BUFFER
}

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.audio;
public class NoAudioEffect extends CustomAudioEffect {

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.decoder;
import android.media.MediaCodec;
@ -6,160 +22,110 @@ import android.media.MediaFormat;
import android.util.Log;
import com.pedro.encoder.Frame;
import com.pedro.encoder.input.audio.GetMicrophoneData;
import com.pedro.encoder.utils.CodecUtil;
import com.pedro.encoder.utils.PCMUtil;
import java.io.IOException;
import java.nio.ByteBuffer;
/**
* Created by pedro on 20/06/17.
*/
public class AudioDecoder {
private final String TAG = "AudioDecoder";
public class AudioDecoder extends BaseDecoder {
private AudioDecoderInterface audioDecoderInterface;
private LoopFileInterface loopFileInterface;
private MediaExtractor audioExtractor;
private MediaCodec audioDecoder;
private MediaCodec.BufferInfo audioInfo = new MediaCodec.BufferInfo();
private boolean decoding;
private Thread thread;
private GetMicrophoneData getMicrophoneData;
private MediaFormat audioFormat;
private String mime = "";
private int sampleRate;
private boolean isStereo;
private int channels = 1;
private int size = 2048;
private byte[] pcmBuffer = new byte[size];
private byte[] pcmBufferMuted = new byte[11];
private static boolean loopMode = false;
private boolean muted = false;
private long duration;
private volatile long seekTime = 0;
private volatile long startMs = 0;
public AudioDecoder(GetMicrophoneData getMicrophoneData,
AudioDecoderInterface audioDecoderInterface, LoopFileInterface loopFileInterface) {
super(loopFileInterface);
TAG = "AudioDecoder";
this.getMicrophoneData = getMicrophoneData;
this.audioDecoderInterface = audioDecoderInterface;
this.loopFileInterface = loopFileInterface;
}
public boolean initExtractor(String filePath) throws IOException {
decoding = false;
audioExtractor = new MediaExtractor();
audioExtractor.setDataSource(filePath);
@Override
protected boolean extract(MediaExtractor audioExtractor) {
size = 2048;
running = false;
for (int i = 0; i < audioExtractor.getTrackCount() && !mime.startsWith("audio/"); i++) {
audioFormat = audioExtractor.getTrackFormat(i);
mime = audioFormat.getString(MediaFormat.KEY_MIME);
mediaFormat = audioExtractor.getTrackFormat(i);
mime = mediaFormat.getString(MediaFormat.KEY_MIME);
if (mime.startsWith("audio/")) {
audioExtractor.selectTrack(i);
} else {
audioFormat = null;
mediaFormat = null;
}
}
if (audioFormat != null) {
channels = audioFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
if (mediaFormat != null) {
channels = mediaFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
isStereo = channels >= 2;
sampleRate = audioFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
duration = audioFormat.getLong(MediaFormat.KEY_DURATION);
if (channels >= 2) {
pcmBuffer = new byte[2048 * channels];
}
sampleRate = mediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
duration = mediaFormat.getLong(MediaFormat.KEY_DURATION);
fixBuffer();
return true;
//audio decoder not supported
} else {
mime = "";
audioFormat = null;
mediaFormat = null;
return false;
}
}
private void fixBuffer() {
if (channels >= 2) {
size *= channels;
}
pcmBuffer = new byte[size];
}
public boolean prepareAudio() {
try {
audioDecoder = MediaCodec.createDecoderByType(mime);
audioDecoder.configure(audioFormat, null, null, 0);
return true;
} catch (IOException e) {
Log.e(TAG, "Prepare decoder error:", e);
return false;
}
return prepare(null);
}
public void start() {
decoding = true;
audioDecoder.start();
thread = new Thread(new Runnable() {
@Override
public void run() {
try {
decodeAudio();
} catch (IllegalStateException e) {
Log.i(TAG, "Decoding error", e);
}
}
});
thread.start();
public void reset() {
resetCodec(null);
}
public void stop() {
decoding = false;
seekTime = 0;
if (thread != null) {
thread.interrupt();
try {
thread.join(100);
} catch (InterruptedException e) {
thread.interrupt();
}
thread = null;
}
try {
audioDecoder.stop();
audioDecoder.release();
audioDecoder = null;
} catch (IllegalStateException | NullPointerException e) {
audioDecoder = null;
}
if (audioExtractor != null) {
audioExtractor.release();
audioExtractor = null;
}
}
private void decodeAudio() throws IllegalStateException {
ByteBuffer[] inputBuffers = audioDecoder.getInputBuffers();
ByteBuffer[] outputBuffers = audioDecoder.getOutputBuffers();
@Override
protected void decode() {
ByteBuffer[] inputBuffers = codec.getInputBuffers();
ByteBuffer[] outputBuffers = codec.getOutputBuffers();
startMs = System.currentTimeMillis();
while (decoding) {
int inIndex = audioDecoder.dequeueInputBuffer(10000);
while (running) {
int inIndex = codec.dequeueInputBuffer(10000);
if (inIndex >= 0) {
ByteBuffer buffer = inputBuffers[inIndex];
int sampleSize = audioExtractor.readSampleData(buffer, 0);
int sampleSize = extractor.readSampleData(buffer, 0);
if (sampleSize < 0) {
audioDecoder.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
codec.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
} else {
audioDecoder.queueInputBuffer(inIndex, 0, sampleSize, audioExtractor.getSampleTime(), 0);
audioExtractor.advance();
codec.queueInputBuffer(inIndex, 0, sampleSize, extractor.getSampleTime(), 0);
extractor.advance();
}
int outIndex = audioDecoder.dequeueOutputBuffer(audioInfo, 10000);
int outIndex = codec.dequeueOutputBuffer(bufferInfo, 10000);
switch (outIndex) {
case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
outputBuffers = audioDecoder.getOutputBuffers();
outputBuffers = codec.getOutputBuffers();
break;
case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
case MediaCodec.INFO_TRY_AGAIN_LATER:
break;
default:
//needed for fix decode speed
while (audioExtractor.getSampleTime() / 1000
> System.currentTimeMillis() - startMs + seekTime) {
long extractorTs = extractor.getSampleTime() / 1000;
long currentTs = System.currentTimeMillis() - startMs + seekTime;
if (extractorTs > currentTs) {
try {
Thread.sleep(10);
long sleepTime = extractorTs - currentTs;
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
if (thread != null) thread.interrupt();
Thread.currentThread().interrupt();
return;
}
}
@ -167,13 +133,13 @@ public class AudioDecoder {
//This buffer is PCM data
if (muted) {
outBuffer.get(pcmBufferMuted, 0,
outBuffer.remaining() <= pcmBufferMuted.length ? outBuffer.remaining()
: pcmBufferMuted.length);
Math.min(outBuffer.remaining(), pcmBufferMuted.length));
getMicrophoneData.inputPCMData(new Frame(pcmBufferMuted, 0, pcmBufferMuted.length));
} else {
outBuffer.get(pcmBuffer, 0,
outBuffer.remaining() <= pcmBuffer.length ? outBuffer.remaining()
: pcmBuffer.length);
if (pcmBuffer.length < outBuffer.remaining()) {
pcmBuffer = new byte[outBuffer.remaining()];
}
outBuffer.get(pcmBuffer, 0, Math.min(outBuffer.remaining(), pcmBuffer.length));
if (channels > 2) { //downgrade to stereo
byte[] bufferStereo = PCMUtil.pcmToStereo(pcmBuffer, channels);
getMicrophoneData.inputPCMData(new Frame(bufferStereo, 0, bufferStereo.length));
@ -181,42 +147,54 @@ public class AudioDecoder {
getMicrophoneData.inputPCMData(new Frame(pcmBuffer, 0, pcmBuffer.length));
}
}
audioDecoder.releaseOutputBuffer(outIndex, false);
codec.releaseOutputBuffer(outIndex, false);
break;
}
}
// All decoded frames have been rendered, we can stop playing now
if ((audioInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
seekTime = 0;
Log.i(TAG, "end of file out");
if (loopMode) {
loopFileInterface.onReset(false);
} else {
audioDecoderInterface.onAudioDecoderFinished();
}
// All decoded frames have been rendered, we can stop playing now
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
seekTime = 0;
Log.i(TAG, "end of file out");
running = false;
if (loopMode) {
loopFileInterface.onReset(false);
} else {
audioDecoderInterface.onAudioDecoderFinished();
}
}
}
}
public double getTime() {
if (decoding) {
return audioExtractor.getSampleTime() / 10E5;
/**
* This method should be called after prepare.
* Get max output size to set max input size in encoder.
*/
public int getOutsize() {
if (!(mime.equals(CodecUtil.AAC_MIME) || mime.equals(CodecUtil.OPUS_MIME) || mime.equals(
CodecUtil.VORBIS_MIME))) {
Log.i(TAG, "fixing input size");
try {
if (running) {
return codec.getOutputBuffers()[0].remaining();
} else {
if (codec != null) {
codec.start();
int outSize = codec.getOutputBuffers()[0].remaining();
stopDecoder();
if (prepare(null)) return outSize;
}
return 0;
}
} catch (Exception e) {
return 0;
}
} else {
Log.i(TAG, "default input size");
return 0;
}
}
public void moveTo(double time) {
audioExtractor.seekTo((long) (time * 10E5), MediaExtractor.SEEK_TO_CLOSEST_SYNC);
seekTime = audioExtractor.getSampleTime() / 1000;
startMs = System.currentTimeMillis();
}
public void setLoopMode(boolean loopMode) {
this.loopMode = loopMode;
}
public void mute() {
muted = true;
}
@ -236,8 +214,4 @@ public class AudioDecoder {
public boolean isStereo() {
return isStereo;
}
public double getDuration() {
return duration / 10E5;
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.decoder;
/**

View File

@ -0,0 +1,216 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.decoder;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.media.MediaCodec;
import android.media.MediaDataSource;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.view.Surface;
import androidx.annotation.RequiresApi;
import java.io.FileDescriptor;
import java.io.IOException;
import java.util.Map;
public abstract class BaseDecoder {
protected static String TAG = "BaseDecoder";
protected LoopFileInterface loopFileInterface;
protected MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
protected MediaExtractor extractor;
protected MediaCodec codec;
protected volatile boolean running = false;
protected MediaFormat mediaFormat;
private HandlerThread handlerThread;
protected String mime = "";
protected boolean loopMode = false;
protected volatile long seekTime = 0;
protected volatile long startMs = 0;
protected long duration;
public BaseDecoder(LoopFileInterface loopFileInterface) {
this.loopFileInterface = loopFileInterface;
}
public boolean initExtractor(String filePath) throws IOException {
extractor = new MediaExtractor();
extractor.setDataSource(filePath);
return extract(extractor);
}
public boolean initExtractor(FileDescriptor fileDescriptor) throws IOException {
extractor = new MediaExtractor();
extractor.setDataSource(fileDescriptor);
return extract(extractor);
}
@RequiresApi(api = Build.VERSION_CODES.N)
public boolean initExtractor(AssetFileDescriptor assetFileDescriptor) throws IOException {
extractor = new MediaExtractor();
extractor.setDataSource(assetFileDescriptor);
return extract(extractor);
}
@RequiresApi(api = Build.VERSION_CODES.M)
public boolean initExtractor(MediaDataSource mediaDataSource) throws IOException {
extractor = new MediaExtractor();
extractor.setDataSource(mediaDataSource);
return extract(extractor);
}
public boolean initExtractor(String filePath, Map<String, String> headers) throws IOException {
extractor = new MediaExtractor();
extractor.setDataSource(filePath, headers);
return extract(extractor);
}
public boolean initExtractor(FileDescriptor fileDescriptor, long offset, long length)
throws IOException {
extractor = new MediaExtractor();
extractor.setDataSource(fileDescriptor, offset, length);
return extract(extractor);
}
public boolean initExtractor(Context context, Uri uri, Map<String, String> headers)
throws IOException {
extractor = new MediaExtractor();
extractor.setDataSource(context, uri, headers);
return extract(extractor);
}
public void start() {
Log.i(TAG, "start decoder");
running = true;
handlerThread = new HandlerThread(TAG);
handlerThread.start();
Handler handler = new Handler(handlerThread.getLooper());
codec.start();
handler.post(new Runnable() {
@Override
public void run() {
try {
decode();
} catch (IllegalStateException e) {
Log.i(TAG, "Decoding error", e);
} catch (NullPointerException e) {
Log.i(TAG, "Decoder maybe was stopped");
Log.i(TAG, "Decoding error", e);
}
}
});
}
public void stop() {
Log.i(TAG, "stop decoder");
running = false;
stopDecoder();
if (extractor != null) {
extractor.release();
extractor = null;
mime = "";
}
}
protected boolean prepare(Surface surface) {
try {
codec = MediaCodec.createDecoderByType(mime);
codec.configure(mediaFormat, surface, null, 0);
return true;
} catch (IOException e) {
Log.e(TAG, "Prepare decoder error:", e);
return false;
}
}
protected void resetCodec(Surface surface) {
boolean wasRunning = running;
stopDecoder();
if (extractor != null) seekTime = extractor.getSampleTime() / 1000;
prepare(surface);
if (wasRunning) {
start();
}
}
protected void stopDecoder() {
running = false;
seekTime = 0;
if (handlerThread != null) {
if (handlerThread.getLooper() != null) {
if (handlerThread.getLooper().getThread() != null) {
handlerThread.getLooper().getThread().interrupt();
}
handlerThread.getLooper().quit();
}
handlerThread.quit();
if (codec != null) {
try {
codec.flush();
} catch (IllegalStateException ignored) { }
}
//wait for thread to die for 500ms.
try {
handlerThread.getLooper().getThread().join(500);
} catch (Exception ignored) { }
handlerThread = null;
}
try {
codec.stop();
codec.release();
codec = null;
} catch (IllegalStateException | NullPointerException e) {
codec = null;
}
}
public void moveTo(double time) {
extractor.seekTo((long) (time * 10E5), MediaExtractor.SEEK_TO_CLOSEST_SYNC);
seekTime = extractor.getSampleTime() / 1000;
startMs = System.currentTimeMillis();
}
public void setLoopMode(boolean loopMode) {
this.loopMode = loopMode;
}
public boolean isLoopMode() {
return loopMode;
}
public double getDuration() {
return duration / 10E5;
}
public double getTime() {
if (running) {
return extractor.getSampleTime() / 10E5;
} else {
return 0;
}
}
protected abstract boolean extract(MediaExtractor extractor);
protected abstract void decode();
}

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.decoder;
/**

View File

@ -1,178 +1,130 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.decoder;
import android.media.MediaCodec;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.os.Build;
import android.util.Log;
import android.view.Surface;
import java.io.IOException;
import java.nio.ByteBuffer;
/**
* Created by pedro on 20/06/17.
*/
public class VideoDecoder {
private final String TAG = "VideoDecoder";
public class VideoDecoder extends BaseDecoder {
private VideoDecoderInterface videoDecoderInterface;
private LoopFileInterface loopFileInterface;
private MediaExtractor videoExtractor;
private MediaCodec videoDecoder;
private MediaCodec.BufferInfo videoInfo = new MediaCodec.BufferInfo();
private boolean decoding;
private Thread thread;
private MediaFormat videoFormat;
private String mime = "";
private int width;
private int height;
private long duration;
private static boolean loopMode = false;
private volatile long seekTime = 0;
private volatile long startMs = 0;
public VideoDecoder(VideoDecoderInterface videoDecoderInterface,
LoopFileInterface loopFileInterface) {
super(loopFileInterface);
TAG = "VideoDecoder";
this.videoDecoderInterface = videoDecoderInterface;
this.loopFileInterface = loopFileInterface;
}
public boolean initExtractor(String filePath) throws IOException {
decoding = false;
videoExtractor = new MediaExtractor();
videoExtractor.setDataSource(filePath);
@Override
protected boolean extract(MediaExtractor videoExtractor) {
running = false;
for (int i = 0; i < videoExtractor.getTrackCount() && !mime.startsWith("video/"); i++) {
videoFormat = videoExtractor.getTrackFormat(i);
mime = videoFormat.getString(MediaFormat.KEY_MIME);
mediaFormat = videoExtractor.getTrackFormat(i);
mime = mediaFormat.getString(MediaFormat.KEY_MIME);
if (mime.startsWith("video/")) {
videoExtractor.selectTrack(i);
} else {
videoFormat = null;
mediaFormat = null;
}
}
if (videoFormat != null) {
width = videoFormat.getInteger(MediaFormat.KEY_WIDTH);
height = videoFormat.getInteger(MediaFormat.KEY_HEIGHT);
duration = videoFormat.getLong(MediaFormat.KEY_DURATION);
if (mediaFormat != null) {
width = mediaFormat.getInteger(MediaFormat.KEY_WIDTH);
height = mediaFormat.getInteger(MediaFormat.KEY_HEIGHT);
duration = mediaFormat.getLong(MediaFormat.KEY_DURATION);
return true;
//video decoder not supported
} else {
mime = "";
videoFormat = null;
mediaFormat = null;
return false;
}
}
public boolean prepareVideo(Surface surface) {
try {
videoDecoder = MediaCodec.createDecoderByType(mime);
videoDecoder.configure(videoFormat, surface, null, 0);
return true;
} catch (IOException e) {
Log.e(TAG, "Prepare decoder error:", e);
return false;
}
return prepare(surface);
}
public void start() {
decoding = true;
videoDecoder.start();
thread = new Thread(new Runnable() {
@Override
public void run() {
try {
decodeVideo();
} catch (IllegalStateException e) {
Log.i(TAG, "Decoding error", e);
}
}
});
thread.start();
public void reset(Surface surface) {
resetCodec(surface);
}
public void stop() {
decoding = false;
seekTime = 0;
if (thread != null) {
thread.interrupt();
try {
thread.join(100);
} catch (InterruptedException e) {
thread.interrupt();
}
thread = null;
}
try {
videoDecoder.stop();
videoDecoder.release();
videoDecoder = null;
} catch (IllegalStateException | NullPointerException e) {
videoDecoder = null;
}
if (videoExtractor != null) {
videoExtractor.release();
videoExtractor = null;
}
}
private void decodeVideo() throws IllegalStateException {
ByteBuffer[] inputBuffers = videoDecoder.getInputBuffers();
@Override
protected void decode() {
ByteBuffer[] inputBuffers = codec.getInputBuffers();
startMs = System.currentTimeMillis();
while (decoding) {
int inIndex = videoDecoder.dequeueInputBuffer(10000);
while (running) {
int inIndex = codec.dequeueInputBuffer(10000);
if (inIndex >= 0) {
ByteBuffer buffer = inputBuffers[inIndex];
int sampleSize = videoExtractor.readSampleData(buffer, 0);
int sampleSize = extractor.readSampleData(buffer, 0);
if (sampleSize < 0) {
videoDecoder.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
codec.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
} else {
videoDecoder.queueInputBuffer(inIndex, 0, sampleSize, videoExtractor.getSampleTime(), 0);
videoExtractor.advance();
codec.queueInputBuffer(inIndex, 0, sampleSize, extractor.getSampleTime(), 0);
extractor.advance();
}
}
int outIndex = videoDecoder.dequeueOutputBuffer(videoInfo, 10000);
int outIndex = codec.dequeueOutputBuffer(bufferInfo, 10000);
if (outIndex >= 0) {
while (videoExtractor.getSampleTime() / 1000
> System.currentTimeMillis() - startMs + seekTime) {
long extractorTs = extractor.getSampleTime() / 1000;
long currentTs = System.currentTimeMillis() - startMs + seekTime;
if (extractorTs > currentTs) {
try {
Thread.sleep(10);
long sleepTime = extractorTs - currentTs;
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
if (thread != null) thread.interrupt();
Thread.currentThread().interrupt();
return;
}
}
videoDecoder.releaseOutputBuffer(outIndex, videoInfo.size != 0);
codec.releaseOutputBuffer(outIndex, bufferInfo.size != 0);
}
if ((videoInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
seekTime = 0;
Log.i(TAG, "end of file out");
if (loopMode) {
loopFileInterface.onReset(true);
} else {
videoDecoderInterface.onVideoDecoderFinished();
}
}
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
seekTime = 0;
Log.i(TAG, "end of file out");
running = false;
if (loopMode) {
loopFileInterface.onReset(true);
} else {
videoDecoderInterface.onVideoDecoderFinished();
}
}
}
public double getTime() {
if (decoding) {
return videoExtractor.getSampleTime() / 10E5;
public void changeOutputSurface(Surface surface) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
codec.setOutputSurface(surface);
} else {
return 0;
reset(surface);
}
}
public void moveTo(double time) {
videoExtractor.seekTo((long) (time * 10E5), MediaExtractor.SEEK_TO_CLOSEST_SYNC);
seekTime = videoExtractor.getSampleTime() / 1000;
startMs = System.currentTimeMillis();
}
public void setLoopMode(boolean loopMode) {
this.loopMode = loopMode;
}
public int getWidth() {
return width;
}
@ -180,8 +132,4 @@ public class VideoDecoder {
public int getHeight() {
return height;
}
public double getDuration() {
return duration / 10E5;
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.decoder;
/**

View File

@ -0,0 +1,157 @@
package com.pedro.encoder.input.gl;
import android.graphics.PointF;
import android.view.View;
import com.pedro.encoder.utils.gl.TranslateTo;
/**
* Created by pedro on 3/2/22.
*/
public class AndroidViewSprite {
private PointF scale;
private PointF position;
private PointF rotationAxis;
private int rotation;
private View view;
public AndroidViewSprite() {
reset();
}
public void setView(View view) {
this.view = view;
rotationAxis = new PointF(view.getMeasuredWidth() / 2f, view.getMeasuredHeight() / 2f);
}
/**
* @param deltaX Position x in percent
* @param deltaY Position x in percent
*/
public void translate(float deltaX, float deltaY) {
position.x = deltaX;
position.y = deltaY;
}
/**
* @param translation Predefined position
*/
public void translate(TranslateTo translation) {
switch (translation) {
case CENTER:
this.position.x = 50f - scale.x / 2f;
this.position.y = 50f - scale.x / 2f;
break;
case BOTTOM:
this.position.x = 50f - scale.x / 2f;
this.position.y = 100f - scale.y;
break;
case TOP:
this.position.x = 50f - scale.x / 2f;
this.position.y = 0f;
break;
case LEFT:
this.position.x = 0f;
this.position.y = 50f - scale.y / 2f;
break;
case RIGHT:
this.position.x = 100f - scale.x;
this.position.y = 50f - scale.y / 2f;
break;
case TOP_LEFT:
this.position.x = 0f;
this.position.y = 0f;
break;
case TOP_RIGHT:
this.position.x = 100f - scale.x;
this.position.y = 0f;
break;
case BOTTOM_LEFT:
this.position.x = 0f;
this.position.y = 100f - scale.y;
break;
case BOTTOM_RIGHT:
this.position.x = 100f - scale.x;
this.position.y = 100f - scale.y;
break;
default:
break;
}
}
/**
* @param deltaX Scale x in percent
* @param deltaY Scale y in percent
*/
public void scale(float deltaX, float deltaY) {
//keep old position
position.x /= deltaX / scale.x;
position.y /= deltaY / scale.y;
//set new scale.
scale = new PointF(deltaX, deltaY);
}
/**
* @return Scale in percent
*/
public PointF getScale() {
return scale;
}
/**
* @return Position in percent
*/
public PointF getTranslation() {
return position;
}
public int getRotation() {
return rotation;
}
public PointF getRotationAxis() {
return rotationAxis;
}
public void setRotation(int rotation) {
if (rotation < 0) {
this.rotation = 0;
} else if (rotation > 360) {
this.rotation = 360;
} else {
this.rotation = rotation;
}
}
public void reset() {
scale = new PointF(0f, 0f);
position = new PointF(0f, 0f);
}
/**
* Traduce position in percent to work with canvas.
*/
public PointF getCanvasPosition(float previewX, float previewY) {
return new PointF(previewX * position.x / 100f, previewY * position.y / 100f);
}
/**
* Traduce scale in percent to work with canvas.
*/
public PointF getCanvasScale(float previewX, float previewY) {
float scaleFactorX = 100f * (float) view.getWidth() / previewX;
float scaleFactorY = 100f * (float) view.getHeight() / previewY;
return new PointF(scale.x / scaleFactorX, scale.y / scaleFactorY);
}
/**
* Calculate default scale if none is indicated after load the filter.
*/
public void calculateDefaultScale(float previewX, float previewY) {
if (scale.x == 0f && scale.y == 0f) {
scale.x = 100f * (float) view.getWidth() / previewX;
scale.y = 100f * (float) view.getHeight() / previewY;
}
}
}

View File

@ -0,0 +1,24 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl;
/**
* Created by pedro on 21/9/21.
*/
public enum FilterAction {
SET, SET_INDEX, ADD, ADD_INDEX, CLEAR, REMOVE, REMOVE_INDEX
}

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl;
import android.graphics.PointF;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl;
import android.graphics.PointF;
@ -5,6 +21,8 @@ import android.os.Build;
import androidx.annotation.RequiresApi;
import android.view.MotionEvent;
import android.view.View;
import com.pedro.encoder.input.gl.render.filters.AndroidViewFilterRender;
import com.pedro.encoder.input.gl.render.filters.BaseFilterRender;
import com.pedro.encoder.input.gl.render.filters.object.BaseObjectFilterRender;
import com.pedro.encoder.input.video.CameraHelper;
@ -16,6 +34,7 @@ import com.pedro.encoder.input.video.CameraHelper;
public class SpriteGestureController {
private BaseObjectFilterRender baseObjectFilterRender;
private AndroidViewFilterRender androidViewFilterRender;
private float lastDistance;
private boolean preventMoveOutside = true;
@ -26,12 +45,27 @@ public class SpriteGestureController {
this.baseObjectFilterRender = sprite;
}
public BaseObjectFilterRender getBaseObjectFilterRender() {
return baseObjectFilterRender;
public SpriteGestureController(AndroidViewFilterRender sprite) {
this.androidViewFilterRender = sprite;
}
public BaseFilterRender getFilterRender() {
return androidViewFilterRender == null ? baseObjectFilterRender : androidViewFilterRender;
}
public void setBaseObjectFilterRender(BaseObjectFilterRender baseObjectFilterRender) {
this.baseObjectFilterRender = baseObjectFilterRender;
this.androidViewFilterRender = null;
}
public void setBaseObjectFilterRender(AndroidViewFilterRender androidViewFilterRender) {
this.androidViewFilterRender = androidViewFilterRender;
this.baseObjectFilterRender = null;
}
public void stopListener() {
this.androidViewFilterRender = null;
this.baseObjectFilterRender = null;
}
public void setPreventMoveOutside(boolean preventMoveOutside) {
@ -39,22 +73,34 @@ public class SpriteGestureController {
}
public boolean spriteTouched(View view, MotionEvent motionEvent) {
if (baseObjectFilterRender == null) return false;
if (baseObjectFilterRender == null && androidViewFilterRender == null) return false;
float xPercent = motionEvent.getX() * 100 / view.getWidth();
float yPercent = motionEvent.getY() * 100 / view.getHeight();
PointF scale = baseObjectFilterRender.getScale();
PointF position = baseObjectFilterRender.getPosition();
PointF scale;
PointF position;
if (baseObjectFilterRender != null) {
scale = baseObjectFilterRender.getScale();
position = baseObjectFilterRender.getPosition();
} else {
scale = androidViewFilterRender.getScale();
position = androidViewFilterRender.getPosition();
}
boolean xTouched = xPercent >= position.x && xPercent <= position.x + scale.x;
boolean yTouched = yPercent >= position.y && yPercent <= position.y + scale.y;
return xTouched && yTouched;
}
public void moveSprite(View view, MotionEvent motionEvent) {
if (baseObjectFilterRender == null) return;
if (baseObjectFilterRender == null && androidViewFilterRender == null) return;
if (motionEvent.getPointerCount() == 1) {
float xPercent = motionEvent.getX() * 100 / view.getWidth();
float yPercent = motionEvent.getY() * 100 / view.getHeight();
PointF scale = baseObjectFilterRender.getScale();
PointF scale;
if (baseObjectFilterRender != null) {
scale = baseObjectFilterRender.getScale();
} else {
scale = androidViewFilterRender.getScale();
}
if (preventMoveOutside) {
float x = xPercent - scale.x / 2.0F;
float y = yPercent - scale.y / 2.0F;
@ -70,22 +116,39 @@ public class SpriteGestureController {
if (y + scale.y > 100.0F) {
y = 100.0F - scale.y;
}
baseObjectFilterRender.setPosition(x, y);
if (baseObjectFilterRender != null) {
baseObjectFilterRender.setPosition(x, y);
} else {
androidViewFilterRender.setPosition(x, y);
}
} else {
baseObjectFilterRender.setPosition(xPercent - scale.x / 2f, yPercent - scale.y / 2f);
if (baseObjectFilterRender != null) {
baseObjectFilterRender.setPosition(xPercent - scale.x / 2f, yPercent - scale.y / 2f);
} else {
androidViewFilterRender.setPosition(xPercent - scale.x / 2f, yPercent - scale.y / 2f);
}
}
}
}
public void scaleSprite(MotionEvent motionEvent) {
if (baseObjectFilterRender == null) return;
if (baseObjectFilterRender == null && androidViewFilterRender == null) return;
if (motionEvent.getPointerCount() > 1) {
float distance = CameraHelper.getFingerSpacing(motionEvent);
float percent = distance >= lastDistance ? 1 : -1;
PointF scale = baseObjectFilterRender.getScale();
PointF scale;
if (baseObjectFilterRender != null) {
scale = baseObjectFilterRender.getScale();
} else {
scale = androidViewFilterRender.getScale();
}
scale.x += percent;
scale.y += percent;
baseObjectFilterRender.setScale(scale.x, scale.y);
if (baseObjectFilterRender != null) {
baseObjectFilterRender.setScale(scale.x, scale.y);
} else {
androidViewFilterRender.setScale(scale.x, scale.y);
}
lastDistance = distance;
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl;
import android.opengl.EGL14;
@ -19,45 +35,29 @@ import com.pedro.encoder.utils.gl.GlUtil;
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
public class SurfaceManager {
private static final String TAG = "SurfaceManager";
private static final int EGL_RECORDABLE_ANDROID = 0x3142;
private EGLContext eglContext = null;
private EGLSurface eglSurface = null;
private EGLDisplay eglDisplay = null;
private EGLContext eglContext = EGL14.EGL_NO_CONTEXT;
private EGLSurface eglSurface = EGL14.EGL_NO_SURFACE;
private EGLDisplay eglDisplay = EGL14.EGL_NO_DISPLAY;
private volatile boolean isReady = false;
/**
* Creates an EGL context and an EGL surface.
*/
public SurfaceManager(Surface surface, SurfaceManager manager) {
eglSetup(surface, manager.eglContext);
}
/**
* Creates an EGL context and an EGL surface.
*/
public SurfaceManager(Surface surface, EGLContext eglContext) {
eglSetup(surface, eglContext);
}
/**
* Creates an EGL context and an EGL surface.
*/
public SurfaceManager(Surface surface) {
eglSetup(surface, null);
}
public SurfaceManager() {
eglSetup(null, null);
public boolean isReady() {
return isReady;
}
public void makeCurrent() {
if (!EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) {
Log.e("Error", "eglMakeCurrent failed");
Log.e(TAG, "eglMakeCurrent failed");
}
}
public void swapBuffer() {
EGL14.eglSwapBuffers(eglDisplay, eglSurface);
if (!EGL14.eglSwapBuffers(eglDisplay, eglSurface)) {
Log.e(TAG, "eglSwapBuffers failed");
}
}
/**
@ -71,7 +71,11 @@ public class SurfaceManager {
/**
* Prepares EGL. We want a GLES 2.0 context and a surface that supports recording.
*/
private void eglSetup(Surface surface, EGLContext eglSharedContext) {
public void eglSetup(int width, int height, Surface surface, EGLContext eglSharedContext) {
if (isReady) {
Log.e(TAG, "already ready, ignored");
return;
}
eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
if (eglDisplay == EGL14.EGL_NO_DISPLAY) {
throw new RuntimeException("unable to get EGL14 display");
@ -83,23 +87,43 @@ public class SurfaceManager {
// Configure EGL for recording and OpenGL ES 2.0.
int[] attribList;
if (eglSharedContext == null) {
if (eglSharedContext == null && surface == null) {
attribList = new int[]{
EGL14.EGL_RED_SIZE, 8, EGL14.EGL_GREEN_SIZE, 8, EGL14.EGL_BLUE_SIZE, 8,
EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT,
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
/* AA https://stackoverflow.com/questions/27035893/antialiasing-in-opengl-es-2-0 */
//EGL14.EGL_SAMPLE_BUFFERS, 1 /* true */,
//EGL14.EGL_SAMPLES, 4, /* increase to more smooth limit of your GPU */
EGL14.EGL_NONE
};
} else if (eglSharedContext == null) {
attribList = new int[]{
EGL14.EGL_RED_SIZE, 8, EGL14.EGL_GREEN_SIZE, 8, EGL14.EGL_BLUE_SIZE, 8,
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
/* AA https://stackoverflow.com/questions/27035893/antialiasing-in-opengl-es-2-0 */
//EGL14.EGL_SAMPLE_BUFFERS, 1 /* true */,
//EGL14.EGL_SAMPLES, 4, /* increase to more smooth limit of your GPU */
EGL14.EGL_NONE
};
} else if (surface == null) {
attribList = new int[] {
EGL14.EGL_RED_SIZE, 8, EGL14.EGL_GREEN_SIZE, 8, EGL14.EGL_BLUE_SIZE, 8,
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
/* AA https://stackoverflow.com/questions/27035893/antialiasing-in-opengl-es-2-0 */
//EGL14.EGL_SAMPLE_BUFFERS, 1 /* true */,
//EGL14.EGL_SAMPLES, 4, /* increase to more smooth limit of your GPU */
EGL14.EGL_NONE
EGL14.EGL_RED_SIZE, 8, EGL14.EGL_GREEN_SIZE, 8, EGL14.EGL_BLUE_SIZE, 8,
EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT,
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, EGL_RECORDABLE_ANDROID, 1,
/* AA https://stackoverflow.com/questions/27035893/antialiasing-in-opengl-es-2-0 */
//EGL14.EGL_SAMPLE_BUFFERS, 1 /* true */,
//EGL14.EGL_SAMPLES, 4, /* increase to more smooth limit of your GPU */
EGL14.EGL_NONE
};
} else {
attribList = new int[] {
EGL14.EGL_RED_SIZE, 8, EGL14.EGL_GREEN_SIZE, 8, EGL14.EGL_BLUE_SIZE, 8,
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, EGL_RECORDABLE_ANDROID, 1,
/* AA https://stackoverflow.com/questions/27035893/antialiasing-in-opengl-es-2-0 */
//EGL14.EGL_SAMPLE_BUFFERS, 1 /* true */,
//EGL14.EGL_SAMPLES, 4, /* increase to more smooth limit of your GPU */
EGL14.EGL_NONE
EGL14.EGL_RED_SIZE, 8, EGL14.EGL_GREEN_SIZE, 8, EGL14.EGL_BLUE_SIZE, 8,
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, EGL_RECORDABLE_ANDROID, 1,
/* AA https://stackoverflow.com/questions/27035893/antialiasing-in-opengl-es-2-0 */
//EGL14.EGL_SAMPLE_BUFFERS, 1 /* true */,
//EGL14.EGL_SAMPLES, 4, /* increase to more smooth limit of your GPU */
EGL14.EGL_NONE
};
}
EGLConfig[] configs = new EGLConfig[1];
@ -119,7 +143,7 @@ public class SurfaceManager {
// Create a window surface, and attach it to the Surface we received.
if (surface == null) {
int[] surfaceAttribs = {
EGL14.EGL_WIDTH, 1, EGL14.EGL_HEIGHT, 1, EGL14.EGL_NONE
EGL14.EGL_WIDTH, width, EGL14.EGL_HEIGHT, height, EGL14.EGL_NONE
};
eglSurface = EGL14.eglCreatePbufferSurface(eglDisplay, configs[0], surfaceAttribs, 0);
} else {
@ -129,6 +153,28 @@ public class SurfaceManager {
eglSurface = EGL14.eglCreateWindowSurface(eglDisplay, configs[0], surface, surfaceAttribs, 0);
}
GlUtil.checkEglError("eglCreateWindowSurface");
isReady = true;
Log.i(TAG, "GL initialized");
}
public void eglSetup(Surface surface, SurfaceManager manager) {
eglSetup(2, 2, surface, manager.eglContext);
}
public void eglSetup(int width, int height, SurfaceManager manager) {
eglSetup(width, height, null, manager.eglContext);
}
public void eglSetup(Surface surface, EGLContext eglContext) {
eglSetup(2, 2, surface, eglContext);
}
public void eglSetup(Surface surface) {
eglSetup(2, 2, surface, null);
}
public void eglSetup() {
eglSetup(2, 2, null, null);
}
/**
@ -142,10 +188,14 @@ public class SurfaceManager {
EGL14.eglDestroyContext(eglDisplay, eglContext);
EGL14.eglReleaseThread();
EGL14.eglTerminate(eglDisplay);
Log.i(TAG, "GL released");
eglDisplay = EGL14.EGL_NO_DISPLAY;
eglContext = EGL14.EGL_NO_CONTEXT;
eglSurface = EGL14.EGL_NO_SURFACE;
isReady = false;
} else {
Log.e(TAG, "GL already released");
}
eglDisplay = EGL14.EGL_NO_DISPLAY;
eglContext = EGL14.EGL_NO_CONTEXT;
eglSurface = EGL14.EGL_NO_SURFACE;
}
public EGLContext getEglContext() {

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl;
import android.graphics.Bitmap;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl.render;
import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl.render;
import android.content.Context;
@ -63,7 +79,7 @@ public class CameraRender extends BaseRenderOffScreen {
uSTMatrixHandle = GLES20.glGetUniformLocation(program, "uSTMatrix");
//camera texture
GlUtil.createExternalTextures(1, textureID, 0);
GlUtil.createExternalTextures(textureID.length, textureID, 0);
surfaceTexture = new SurfaceTexture(textureID[0]);
surfaceTexture.setDefaultBufferSize(width, height);
surface = new Surface(surfaceTexture);
@ -107,8 +123,8 @@ public class CameraRender extends BaseRenderOffScreen {
@Override
public void release() {
GLES20.glDeleteProgram(program);
surfaceTexture = null;
surface = null;
surfaceTexture.release();
surface.release();
}
public void updateTexImage() {

View File

@ -0,0 +1,177 @@
package com.pedro.encoder.input.gl.render
import android.content.Context
import android.graphics.SurfaceTexture
import android.os.Build
import android.view.Surface
import androidx.annotation.RequiresApi
import com.pedro.encoder.input.gl.FilterAction
import com.pedro.encoder.input.gl.render.filters.BaseFilterRender
/**
* Created by pedro on 20/3/22.
*/
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
class MainRender {
private val cameraRender = CameraRender()
private val screenRender = ScreenRender()
private var width = 0
private var height = 0
private var previewWidth = 0
private var previewHeight = 0
private var context: Context? = null
private var filterRenders: MutableList<BaseFilterRender> = ArrayList()
fun initGl(context: Context, encoderWidth: Int, encoderHeight: Int, previewWidth: Int, previewHeight: Int) {
this.context = context
width = encoderWidth
height = encoderHeight
this.previewWidth = previewWidth
this.previewHeight = previewHeight
cameraRender.initGl(width, height, context, previewWidth, previewHeight)
screenRender.setStreamSize(encoderWidth, encoderHeight)
screenRender.setTexId(cameraRender.texId)
screenRender.initGl(context)
}
fun drawOffScreen() {
cameraRender.draw()
for (baseFilterRender in filterRenders) baseFilterRender.draw()
}
fun drawScreen(width: Int, height: Int, keepAspectRatio: Boolean, mode: Int, rotation: Int,
flipStreamVertical: Boolean, flipStreamHorizontal: Boolean) {
screenRender.draw(width, height, keepAspectRatio, mode, rotation, flipStreamVertical,
flipStreamHorizontal)
}
fun drawScreenEncoder(width: Int, height: Int, isPortrait: Boolean, rotation: Int,
flipStreamVertical: Boolean, flipStreamHorizontal: Boolean) {
screenRender.drawEncoder(width, height, isPortrait, rotation, flipStreamVertical,
flipStreamHorizontal)
}
fun drawScreenPreview(width: Int, height: Int, isPortrait: Boolean, keepAspectRatio: Boolean,
mode: Int, rotation: Int, flipStreamVertical: Boolean, flipStreamHorizontal: Boolean) {
screenRender.drawPreview(width, height, isPortrait, keepAspectRatio, mode, rotation,
flipStreamVertical, flipStreamHorizontal)
}
fun release() {
cameraRender.release()
for (baseFilterRender in filterRenders) baseFilterRender.release()
filterRenders.clear()
screenRender.release()
}
private fun setFilter(position: Int, baseFilterRender: BaseFilterRender) {
val id = filterRenders[position].previousTexId
val renderHandler = filterRenders[position].renderHandler
filterRenders[position].release()
filterRenders[position] = baseFilterRender
filterRenders[position].previousTexId = id
filterRenders[position].initGl(width, height, context, previewWidth, previewHeight)
filterRenders[position].renderHandler = renderHandler
}
private fun addFilter(baseFilterRender: BaseFilterRender) {
filterRenders.add(baseFilterRender)
baseFilterRender.initGl(width, height, context, previewWidth, previewHeight)
baseFilterRender.initFBOLink()
reOrderFilters()
}
private fun addFilter(position: Int, baseFilterRender: BaseFilterRender) {
filterRenders.add(position, baseFilterRender)
baseFilterRender.initGl(width, height, context, previewWidth, previewHeight)
baseFilterRender.initFBOLink()
reOrderFilters()
}
private fun clearFilters() {
for (baseFilterRender in filterRenders) {
baseFilterRender.release()
}
filterRenders.clear()
reOrderFilters()
}
private fun removeFilter(position: Int) {
filterRenders.removeAt(position).release()
reOrderFilters()
}
private fun removeFilter(baseFilterRender: BaseFilterRender) {
baseFilterRender.release()
filterRenders.remove(baseFilterRender)
reOrderFilters()
}
private fun reOrderFilters() {
for (i in filterRenders.indices) {
val texId = if (i == 0) cameraRender.texId else filterRenders[i - 1].texId
filterRenders[i].previousTexId = texId
}
val texId = if (filterRenders.isEmpty()) {
cameraRender.texId
} else {
filterRenders[filterRenders.size - 1].texId
}
screenRender.setTexId(texId)
}
fun setFilterAction(filterAction: FilterAction?, position: Int, baseFilterRender: BaseFilterRender) {
when (filterAction) {
FilterAction.SET -> if (filterRenders.size > 0) {
setFilter(position, baseFilterRender)
} else {
addFilter(baseFilterRender)
}
FilterAction.SET_INDEX -> setFilter(position, baseFilterRender)
FilterAction.ADD -> addFilter(baseFilterRender)
FilterAction.ADD_INDEX -> addFilter(position, baseFilterRender)
FilterAction.CLEAR -> clearFilters()
FilterAction.REMOVE -> removeFilter(baseFilterRender)
FilterAction.REMOVE_INDEX -> removeFilter(position)
else -> {}
}
}
fun filtersCount(): Int {
return filterRenders.size
}
fun setPreviewSize(previewWidth: Int, previewHeight: Int) {
for (i in filterRenders.indices) {
filterRenders[i].setPreviewSize(previewWidth, previewHeight)
}
}
fun enableAA(AAEnabled: Boolean) {
screenRender.isAAEnabled = AAEnabled
}
fun isAAEnabled(): Boolean {
return screenRender.isAAEnabled
}
fun updateFrame() {
cameraRender.updateTexImage()
}
fun getSurfaceTexture(): SurfaceTexture {
return cameraRender.surfaceTexture
}
fun getSurface(): Surface {
return cameraRender.surface
}
fun setCameraRotation(rotation: Int) {
cameraRender.setRotation(rotation)
}
fun setCameraFlip(isFlipHorizontal: Boolean, isFlipVertical: Boolean) {
cameraRender.setFlip(isFlipHorizontal, isFlipVertical)
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl.render;
import android.content.Context;
@ -5,8 +21,9 @@ import android.graphics.SurfaceTexture;
import android.os.Build;
import android.view.Surface;
import androidx.annotation.RequiresApi;
import com.pedro.encoder.input.gl.FilterAction;
import com.pedro.encoder.input.gl.render.filters.BaseFilterRender;
import com.pedro.encoder.input.gl.render.filters.NoFilterRender;
import java.util.ArrayList;
import java.util.List;
@ -17,13 +34,12 @@ import java.util.List;
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
public class ManagerRender {
//Increase it to render more than 1 filter and set filter by position.
// You must modify it before create your rtmp or rtsp object.
public static int numFilters = 1;
//Set filter limit. If the number is 0 or less you can add infinity filters
public static int numFilters = 0;
private CameraRender cameraRender;
private List<BaseFilterRender> baseFilterRender = new ArrayList<>(numFilters);
private ScreenRender screenRender;
private final CameraRender cameraRender;
private final List<BaseFilterRender> filterRenders;
private final ScreenRender screenRender;
private int width;
private int height;
@ -32,8 +48,8 @@ public class ManagerRender {
private Context context;
public ManagerRender() {
filterRenders = new ArrayList<>();
cameraRender = new CameraRender();
for (int i = 0; i < numFilters; i++) baseFilterRender.add(new NoFilterRender());
screenRender = new ScreenRender();
}
@ -45,33 +61,26 @@ public class ManagerRender {
this.previewWidth = previewWidth;
this.previewHeight = previewHeight;
cameraRender.initGl(width, height, context, previewWidth, previewHeight);
for (int i = 0; i < numFilters; i++) {
int textId = i == 0 ? cameraRender.getTexId() : baseFilterRender.get(i - 1).getTexId();
baseFilterRender.get(i).setPreviousTexId(textId);
baseFilterRender.get(i).initGl(width, height, context, previewWidth, previewHeight);
baseFilterRender.get(i).initFBOLink();
}
screenRender.setStreamSize(encoderWidth, encoderHeight);
screenRender.setTexId(baseFilterRender.get(numFilters - 1).getTexId());
screenRender.setTexId(cameraRender.getTexId());
screenRender.initGl(context);
}
public void drawOffScreen() {
cameraRender.draw();
for (BaseFilterRender baseFilterRender : baseFilterRender) baseFilterRender.draw();
for (BaseFilterRender baseFilterRender : filterRenders) baseFilterRender.draw();
}
public void drawScreen(int width, int height, boolean keepAspectRatio, int mode, int rotation,
boolean isPreview) {
screenRender.draw(width, height, keepAspectRatio, mode, rotation, isPreview);
boolean flipStreamVertical, boolean flipStreamHorizontal) {
screenRender.draw(width, height, keepAspectRatio, mode, rotation, flipStreamVertical,
flipStreamHorizontal);
}
public void release() {
cameraRender.release();
for (int i = 0; i < this.baseFilterRender.size(); i++) {
this.baseFilterRender.get(i).release();
this.baseFilterRender.set(i, new NoFilterRender());
}
for (BaseFilterRender baseFilterRender : filterRenders) baseFilterRender.release();
filterRenders.clear();
screenRender.release();
}
@ -95,14 +104,99 @@ public class ManagerRender {
return cameraRender.getSurface();
}
public void setFilter(int position, BaseFilterRender baseFilterRender) {
final int id = this.baseFilterRender.get(position).getPreviousTexId();
final RenderHandler renderHandler = this.baseFilterRender.get(position).getRenderHandler();
this.baseFilterRender.get(position).release();
this.baseFilterRender.set(position, baseFilterRender);
this.baseFilterRender.get(position).setPreviousTexId(id);
this.baseFilterRender.get(position).initGl(width, height, context, previewWidth, previewHeight);
this.baseFilterRender.get(position).setRenderHandler(renderHandler);
private void setFilter(int position, BaseFilterRender baseFilterRender) {
final int id = filterRenders.get(position).getPreviousTexId();
final RenderHandler renderHandler = filterRenders.get(position).getRenderHandler();
filterRenders.get(position).release();
filterRenders.set(position, baseFilterRender);
filterRenders.get(position).setPreviousTexId(id);
filterRenders.get(position).initGl(width, height, context, previewWidth, previewHeight);
filterRenders.get(position).setRenderHandler(renderHandler);
}
private void addFilter(BaseFilterRender baseFilterRender) {
filterRenders.add(baseFilterRender);
baseFilterRender.initGl(width, height, context, previewWidth, previewHeight);
baseFilterRender.initFBOLink();
reOrderFilters();
}
private void addFilter(int position, BaseFilterRender baseFilterRender) {
filterRenders.add(position, baseFilterRender);
baseFilterRender.initGl(width, height, context, previewWidth, previewHeight);
baseFilterRender.initFBOLink();
reOrderFilters();
}
private void clearFilters() {
for (BaseFilterRender baseFilterRender: filterRenders) {
baseFilterRender.release();
}
filterRenders.clear();
reOrderFilters();
}
private void removeFilter(int position) {
filterRenders.remove(position).release();
reOrderFilters();
}
private void removeFilter(BaseFilterRender baseFilterRender) {
baseFilterRender.release();
filterRenders.remove(baseFilterRender);
reOrderFilters();
}
private void reOrderFilters() {
for (int i = 0; i < filterRenders.size(); i++) {
int texId = i == 0 ? cameraRender.getTexId() : filterRenders.get(i - 1).getTexId();
filterRenders.get(i).setPreviousTexId(texId);
}
int texId = filterRenders.isEmpty() ? cameraRender.getTexId() :
filterRenders.get(filterRenders.size() - 1).getTexId();
screenRender.setTexId(texId);
}
public void setFilterAction(FilterAction filterAction, int position, BaseFilterRender baseFilterRender) {
switch (filterAction) {
case SET:
if (filterRenders.size() > 0) {
setFilter(position, baseFilterRender);
} else {
addFilter(baseFilterRender);
}
break;
case SET_INDEX:
setFilter(position, baseFilterRender);
break;
case ADD:
if (numFilters > 0 && filterRenders.size() >= numFilters) {
throw new RuntimeException("limit of filters(" + numFilters + ") exceeded");
}
addFilter(baseFilterRender);
break;
case ADD_INDEX:
if (numFilters > 0 && filterRenders.size() >= numFilters) {
throw new RuntimeException("limit of filters(" + numFilters + ") exceeded");
}
addFilter(position, baseFilterRender);
break;
case CLEAR:
clearFilters();
break;
case REMOVE:
removeFilter(baseFilterRender);
break;
case REMOVE_INDEX:
removeFilter(position);
break;
default:
break;
}
}
public int filtersCount() {
return filterRenders.size();
}
public void setCameraRotation(int rotation) {
@ -114,8 +208,8 @@ public class ManagerRender {
}
public void setPreviewSize(int previewWidth, int previewHeight) {
for (int i = 0; i < this.baseFilterRender.size(); i++) {
this.baseFilterRender.get(i).setPreviewSize(previewWidth, previewHeight);
for (int i = 0; i < filterRenders.size(); i++) {
filterRenders.get(i).setPreviewSize(previewWidth, previewHeight);
}
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl.render;
/**

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl.render;
import android.content.Context;
@ -22,11 +38,11 @@ public class ScreenRender {
//rotation matrix
private final float[] squareVertexData = {
// X, Y, Z, U, V
-1f, -1f, 0f, 0f, 0f, //bottom left
1f, -1f, 0f, 1f, 0f, //bottom right
-1f, 1f, 0f, 0f, 1f, //top left
1f, 1f, 0f, 1f, 1f, //top right
// X, Y, Z, U, V
-1f, -1f, 0f, 0f, 0f, //bottom left
1f, -1f, 0f, 1f, 0f, //bottom right
-1f, 1f, 0f, 0f, 1f, //top left
1f, 1f, 0f, 1f, 1f, //top right
};
private FloatBuffer squareVertex;
@ -48,20 +64,18 @@ public class ScreenRender {
private int streamWidth;
private int streamHeight;
private boolean isPortrait;
public ScreenRender() {
squareVertex =
ByteBuffer.allocateDirect(squareVertexData.length * BaseRenderOffScreen.FLOAT_SIZE_BYTES)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
ByteBuffer.allocateDirect(squareVertexData.length * BaseRenderOffScreen.FLOAT_SIZE_BYTES)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
squareVertex.put(squareVertexData).position(0);
Matrix.setIdentityM(MVPMatrix, 0);
Matrix.setIdentityM(STMatrix, 0);
}
public void initGl(Context context) {
isPortrait = CameraHelper.isPortrait(context);
GlUtil.checkGlError("initGl start");
String vertexShader = GlUtil.getStringFromRaw(context, R.raw.simple_vertex);
String fragmentShader = GlUtil.getStringFromRaw(context, R.raw.fxaa);
@ -78,15 +92,47 @@ public class ScreenRender {
}
public void draw(int width, int height, boolean keepAspectRatio, int mode, int rotation,
boolean isPreview) {
boolean flipStreamVertical, boolean flipStreamHorizontal) {
GlUtil.checkGlError("drawScreen start");
if (mode == 2 || mode == 3) { //stream rotation is enabled
SizeCalculator.updateMatrix(rotation, width, height, isPreview, isPortrait, MVPMatrix);
}
SizeCalculator.processMatrix(rotation, flipStreamHorizontal, flipStreamVertical,width,height,true,keepAspectRatio, MVPMatrix);
SizeCalculator.calculateViewPort(keepAspectRatio, mode, width, height, streamWidth,
streamHeight);
streamHeight);
draw(width, height);
}
public void drawEncoder(int width, int height, boolean isPortrait, int rotation,
boolean flipStreamVertical, boolean flipStreamHorizontal) {
GlUtil.checkGlError("drawScreen start");
SizeCalculator.processMatrix(rotation, flipStreamHorizontal, flipStreamVertical,width, height, isPortrait,false, MVPMatrix);
SizeCalculator.calculateViewPortEncoder(width, height, isPortrait);
draw(width, height);
}
public void drawPreview(int width, int height, boolean isPortrait, boolean keepAspectRatio,
int mode, int rotation, boolean flipStreamVertical, boolean flipStreamHorizontal) {
GlUtil.checkGlError("drawScreen start");
SizeCalculator.processMatrix(rotation, flipStreamHorizontal, flipStreamVertical, width, height, isPortrait,true, MVPMatrix);
float factor = (float) streamWidth / (float) streamHeight;
int w;
int h;
if (factor >= 1f) {
w = isPortrait ? streamHeight : streamWidth;
h = isPortrait ? streamWidth : streamHeight;
} else {
w = isPortrait ? streamWidth : streamHeight;
h = isPortrait ? streamHeight : streamWidth;
}
SizeCalculator.calculateViewPort(keepAspectRatio, mode, width, height, w, h);
draw(width, height);
}
private void draw(int width, int height) {
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
@ -94,12 +140,12 @@ public class ScreenRender {
squareVertex.position(BaseRenderOffScreen.SQUARE_VERTEX_DATA_POS_OFFSET);
GLES20.glVertexAttribPointer(aPositionHandle, 3, GLES20.GL_FLOAT, false,
BaseRenderOffScreen.SQUARE_VERTEX_DATA_STRIDE_BYTES, squareVertex);
BaseRenderOffScreen.SQUARE_VERTEX_DATA_STRIDE_BYTES, squareVertex);
GLES20.glEnableVertexAttribArray(aPositionHandle);
squareVertex.position(BaseRenderOffScreen.SQUARE_VERTEX_DATA_UV_OFFSET);
GLES20.glVertexAttribPointer(aTextureHandle, 2, GLES20.GL_FLOAT, false,
BaseRenderOffScreen.SQUARE_VERTEX_DATA_STRIDE_BYTES, squareVertex);
BaseRenderOffScreen.SQUARE_VERTEX_DATA_STRIDE_BYTES, squareVertex);
GLES20.glEnableVertexAttribArray(aTextureHandle);
GLES20.glUniformMatrix4fv(uMVPMatrixHandle, 1, false, MVPMatrix, 0);

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl.render;
import android.content.Context;
@ -49,7 +65,6 @@ public class SimpleCameraRender {
private Surface surface;
private int streamWidth;
private int streamHeight;
private boolean isPortrait;
public SimpleCameraRender() {
Matrix.setIdentityM(MVPMatrix, 0);
@ -86,13 +101,11 @@ public class SimpleCameraRender {
}
public void drawFrame(int width, int height, boolean keepAspectRatio, int mode, int rotation,
boolean isPreview) {
boolean flipStreamVertical, boolean flipStreamHorizontal) {
GlUtil.checkGlError("drawFrame start");
surfaceTexture.getTransformMatrix(STMatrix);
if (mode == 2 || mode == 3) { //stream rotation is enabled
SizeCalculator.updateMatrix(rotation, width, height, isPreview, isPortrait, MVPMatrix);
}
SizeCalculator.processMatrix(rotation, flipStreamHorizontal, flipStreamVertical, width, height, true, false, MVPMatrix);
SizeCalculator.calculateViewPort(keepAspectRatio, mode, width, height, streamWidth,
streamHeight);
@ -125,7 +138,6 @@ public class SimpleCameraRender {
* Initializes GL state. Call this after the EGL surface has been created and made current.
*/
public void initGl(Context context, int streamWidth, int streamHeight) {
isPortrait = CameraHelper.isPortrait(context);
this.streamWidth = streamWidth;
this.streamHeight = streamHeight;
GlUtil.checkGlError("initGl start");
@ -139,7 +151,7 @@ public class SimpleCameraRender {
uSTMatrixHandle = GLES20.glGetUniformLocation(program, "uSTMatrix");
//camera texture
GlUtil.createExternalTextures(1, texturesID, 0);
GlUtil.createExternalTextures(texturesID.length, texturesID, 0);
textureID = texturesID[0];
surfaceTexture = new SurfaceTexture(textureID);
@ -150,8 +162,8 @@ public class SimpleCameraRender {
public void release() {
GLES20.glDeleteProgram(program);
surfaceTexture = null;
surface = null;
surfaceTexture.release();
surface.release();
}
public void setFlip(boolean isFlipHorizontal, boolean isFlipVertical) {

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl.render.filters;
import android.content.Context;

View File

@ -1,8 +1,25 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl.render.filters;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PointF;
import android.graphics.PorterDuff;
import android.graphics.SurfaceTexture;
import android.opengl.GLES11Ext;
@ -15,10 +32,13 @@ import android.view.Surface;
import android.view.View;
import androidx.annotation.RequiresApi;
import com.pedro.encoder.R;
import com.pedro.encoder.input.gl.AndroidViewSprite;
import com.pedro.encoder.utils.gl.GlUtil;
import com.pedro.encoder.utils.gl.TranslateTo;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by pedro on 4/02/18.
@ -44,16 +64,21 @@ public class AndroidViewFilterRender extends BaseFilterRender {
private int uSamplerHandle = -1;
private int uSamplerViewHandle = -1;
private int[] viewId = new int[1];
private int[] viewId = new int[] { -1, -1 };
private View view;
private SurfaceTexture surfaceTexture;
private Surface surface;
private Handler mainHandler;
//Use 2 surfaces to avoid block render thread
private SurfaceTexture surfaceTexture, surfaceTexture2;
private Surface surface, surface2;
private final Handler mainHandler;
private boolean running = false;
private ExecutorService thread = null;
private boolean hardwareMode = true;
private final AndroidViewSprite sprite;
private volatile Status renderingStatus = Status.DONE1;
private int rotation;
private float positionX, positionY;
private float scaleX = 1f, scaleY = 1f;
private float viewX, viewY;
private enum Status {
RENDER1, RENDER2, DONE1, DONE2
}
public AndroidViewFilterRender() {
squareVertex = ByteBuffer.allocateDirect(squareVertexDataFilter.length * FLOAT_SIZE_BYTES)
@ -62,6 +87,7 @@ public class AndroidViewFilterRender extends BaseFilterRender {
squareVertex.put(squareVertexDataFilter).position(0);
Matrix.setIdentityM(MVPMatrix, 0);
Matrix.setIdentityM(STMatrix, 0);
sprite = new AndroidViewSprite();
mainHandler = new Handler(Looper.getMainLooper());
}
@ -78,31 +104,37 @@ public class AndroidViewFilterRender extends BaseFilterRender {
uSamplerHandle = GLES20.glGetUniformLocation(program, "uSampler");
uSamplerViewHandle = GLES20.glGetUniformLocation(program, "uSamplerView");
GlUtil.createExternalTextures(1, viewId, 0);
GlUtil.createExternalTextures(viewId.length, viewId, 0);
surfaceTexture = new SurfaceTexture(viewId[0]);
surfaceTexture2 = new SurfaceTexture(viewId[1]);
surface = new Surface(surfaceTexture);
surface2 = new Surface(surfaceTexture2);
}
@Override
protected void drawFilter() {
surfaceTexture.setDefaultBufferSize(getPreviewWidth(), getPreviewHeight());
if (view != null) {
mainHandler.post(new Runnable() {
@Override
public void run() {
Canvas canvas = surface.lockCanvas(null);
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
canvas.translate(positionX, positionY);
canvas.rotate(rotation, viewX / 2f, viewY / 2f);
float scaleFactorX = (float) getPreviewWidth() / (float) view.getWidth();
float scaleFactorY = (float) getPreviewHeight() / (float) view.getHeight();
canvas.scale(scaleX * scaleFactorX, scaleY * scaleFactorY);
view.draw(canvas);
surface.unlockCanvasAndPost(canvas);
}
});
final Status status = renderingStatus;
switch (status) {
case DONE1:
surfaceTexture.setDefaultBufferSize(getPreviewWidth(), getPreviewHeight());
surfaceTexture.updateTexImage();
renderingStatus = Status.RENDER2;
break;
case DONE2:
surfaceTexture2.setDefaultBufferSize(getPreviewWidth(), getPreviewHeight());
surfaceTexture2.updateTexImage();
renderingStatus = Status.RENDER1;
break;
case RENDER1:
surfaceTexture2.setDefaultBufferSize(getPreviewWidth(), getPreviewHeight());
surfaceTexture2.updateTexImage();
break;
case RENDER2:
default:
surfaceTexture.setDefaultBufferSize(getPreviewWidth(), getPreviewHeight());
surfaceTexture.updateTexImage();
break;
}
surfaceTexture.updateTexImage();
GLES20.glUseProgram(program);
@ -125,12 +157,27 @@ public class AndroidViewFilterRender extends BaseFilterRender {
//android view
GLES20.glUniform1i(uSamplerViewHandle, 5);
GLES20.glActiveTexture(GLES20.GL_TEXTURE5);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, viewId[0]);
switch (status) {
case DONE2:
case RENDER1:
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, viewId[1]);
break;
case RENDER2:
case DONE1:
default:
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, viewId[0]);
break;
}
}
@Override
public void release() {
stopRender();
GLES20.glDeleteProgram(program);
viewId = new int[] { -1, -1 };
surfaceTexture.release();
surfaceTexture2.release();
}
public View getView() {
@ -138,11 +185,12 @@ public class AndroidViewFilterRender extends BaseFilterRender {
}
public void setView(final View view) {
stopRender();
this.view = view;
if (view != null) {
view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
viewX = view.getMeasuredWidth();
viewY = view.getMeasuredHeight();
sprite.setView(view);
startRender();
}
}
@ -152,68 +200,105 @@ public class AndroidViewFilterRender extends BaseFilterRender {
* @param y Position in percent
*/
public void setPosition(float x, float y) {
int previewX = getPreviewWidth();
int previewY = getPreviewHeight();
this.positionX = previewX * x / 100f;
this.positionY = previewY * y / 100f;
sprite.translate(x, y);
}
public void setPosition(TranslateTo positionTo) {
int previewX = getPreviewWidth();
int previewY = getPreviewHeight();
switch (positionTo) {
case TOP:
this.positionX = previewX / 2f - (viewX / 2f);
this.positionY = 0f;
break;
case LEFT:
this.positionX = 0;
this.positionY = previewY / 2f - (viewY / 2f);
break;
case RIGHT:
this.positionX = previewX - viewX;
this.positionY = previewY / 2f - (viewY / 2f);
break;
case BOTTOM:
this.positionX = previewX / 2f - (viewX / 2f);
this.positionY = previewY - viewY;
break;
case CENTER:
this.positionX = previewX / 2f - (viewX / 2f);
this.positionY = previewY / 2f - (viewY / 2f);
break;
case TOP_RIGHT:
this.positionX = previewX - viewX;
this.positionY = 0;
break;
case BOTTOM_LEFT:
this.positionX = 0;
this.positionY = previewY - viewY;
break;
case BOTTOM_RIGHT:
this.positionX = previewX - viewX;
this.positionY = previewY - viewY;
break;
case TOP_LEFT:
default:
this.positionX = 0;
this.positionY = 0;
break;
}
sprite.translate(positionTo);
}
public void setRotation(int rotation) {
if (rotation < 0) {
this.rotation = 0;
} else if (rotation > 360) {
this.rotation = 360;
} else {
this.rotation = rotation;
}
sprite.setRotation(rotation);
}
public void setScale(float scaleX, float scaleY) {
this.scaleX = scaleX;
this.scaleY = scaleY;
sprite.scale(scaleX, scaleY);
}
}
public PointF getScale() {
return sprite.getScale();
}
public PointF getPosition() {
return sprite.getTranslation();
}
public int getRotation() {
return sprite.getRotation();
}
public boolean isHardwareMode() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && hardwareMode;
}
/**
* Draw in surface using hardware canvas. True by default
*/
public void setHardwareMode(boolean hardwareMode) {
this.hardwareMode = hardwareMode;
}
private void startRender() {
running = true;
thread = Executors.newSingleThreadExecutor();
thread.execute(() -> {
while (running) {
final Status status = renderingStatus;
if (status == Status.RENDER1 || status == Status.RENDER2) {
final Canvas canvas;
try {
if (isHardwareMode()) {
canvas = status == Status.RENDER1 ? surface.lockHardwareCanvas() : surface2.lockHardwareCanvas();
} else {
canvas = status == Status.RENDER1 ? surface.lockCanvas(null) : surface2.lockCanvas(null);
}
} catch (IllegalStateException e) {
continue;
}
sprite.calculateDefaultScale(getPreviewWidth(), getPreviewHeight());
PointF canvasPosition = sprite.getCanvasPosition(getPreviewWidth(), getPreviewHeight());
PointF canvasScale = sprite.getCanvasScale(getPreviewWidth(), getPreviewHeight());
PointF rotationAxis = sprite.getRotationAxis();
int rotation = sprite.getRotation();
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
canvas.translate(canvasPosition.x, canvasPosition.y);
canvas.scale(canvasScale.x, canvasScale.y);
canvas.rotate(rotation, rotationAxis.x, rotationAxis.y);
try {
view.draw(canvas);
if (status == Status.RENDER1) {
surface.unlockCanvasAndPost(canvas);
renderingStatus = Status.DONE1;
} else {
surface2.unlockCanvasAndPost(canvas);
renderingStatus = Status.DONE2;
}
//Sometimes draw could crash if you don't use main thread. Ensuring you can render always
} catch (Exception e) {
mainHandler.post(() -> {
view.draw(canvas);
if (status == Status.RENDER1) {
surface.unlockCanvasAndPost(canvas);
renderingStatus = Status.DONE1;
} else {
surface2.unlockCanvasAndPost(canvas);
renderingStatus = Status.DONE2;
}
});
}
}
}
});
}
private void stopRender() {
running = false;
if (thread != null) {
thread.shutdownNow();
thread = null;
}
renderingStatus = Status.DONE1;
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl.render.filters;
import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl.render.filters;
import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl.render.filters;
import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl.render.filters;
import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl.render.filters;
import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl.render.filters;
import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl.render.filters;
import android.content.Context;

View File

@ -0,0 +1,173 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl.render.filters;
import android.content.Context;
import android.graphics.Bitmap;
import android.opengl.GLES20;
import android.opengl.Matrix;
import android.os.Build;
import androidx.annotation.RequiresApi;
import com.pedro.encoder.R;
import com.pedro.encoder.input.gl.Sprite;
import com.pedro.encoder.input.gl.TextureLoader;
import com.pedro.encoder.utils.gl.GlUtil;
import com.pedro.encoder.utils.gl.ImageStreamObject;
import com.pedro.encoder.utils.gl.StreamObjectBase;
import com.pedro.encoder.utils.gl.TranslateTo;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
public class ChromaFilterRender extends BaseFilterRender {
//rotation matrix
private final float[] squareVertexDataFilter = {
// X, Y, Z, U, V
-1f, -1f, 0f, 0f, 0f, //bottom left
1f, -1f, 0f, 1f, 0f, //bottom right
-1f, 1f, 0f, 0f, 1f, //top left
1f, 1f, 0f, 1f, 1f, //top right
};
private int program = -1;
private int aPositionHandle = -1;
private int aTextureHandle = -1;
private int aTextureObjectHandle = -1;
private int uMVPMatrixHandle = -1;
private int uSTMatrixHandle = -1;
private int uSamplerHandle = -1;
private int uObjectHandle = -1;
private int uSensitiveHandle = -1;
private FloatBuffer squareVertexObject;
protected int[] streamObjectTextureId = new int[] { -1 };
protected TextureLoader textureLoader = new TextureLoader();
protected StreamObjectBase streamObject;
private Sprite sprite;
protected boolean shouldLoad = false;
private float sensitive = 0.8f;
public ChromaFilterRender() {
streamObject = new ImageStreamObject();
squareVertex = ByteBuffer.allocateDirect(squareVertexDataFilter.length * FLOAT_SIZE_BYTES)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
squareVertex.put(squareVertexDataFilter).position(0);
sprite = new Sprite();
float[] vertices = sprite.getTransformedVertices();
squareVertexObject = ByteBuffer.allocateDirect(vertices.length * FLOAT_SIZE_BYTES)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
squareVertexObject.put(vertices).position(0);
Matrix.setIdentityM(MVPMatrix, 0);
Matrix.setIdentityM(STMatrix, 0);
}
@Override
protected void initGlFilter(Context context) {
String vertexShader = GlUtil.getStringFromRaw(context, R.raw.object_vertex);
String fragmentShader = GlUtil.getStringFromRaw(context, R.raw.chroma_fragment);
program = GlUtil.createProgram(vertexShader, fragmentShader);
aPositionHandle = GLES20.glGetAttribLocation(program, "aPosition");
aTextureHandle = GLES20.glGetAttribLocation(program, "aTextureCoord");
aTextureObjectHandle = GLES20.glGetAttribLocation(program, "aTextureObjectCoord");
uMVPMatrixHandle = GLES20.glGetUniformLocation(program, "uMVPMatrix");
uSTMatrixHandle = GLES20.glGetUniformLocation(program, "uSTMatrix");
uSamplerHandle = GLES20.glGetUniformLocation(program, "uSampler");
uObjectHandle = GLES20.glGetUniformLocation(program, "uObject");
uSensitiveHandle = GLES20.glGetUniformLocation(program, "uSensitive");
}
@Override
protected void drawFilter() {
if (shouldLoad) {
releaseTexture();
streamObjectTextureId = textureLoader.load(streamObject.getBitmaps());
shouldLoad = false;
}
GLES20.glUseProgram(program);
squareVertex.position(SQUARE_VERTEX_DATA_POS_OFFSET);
GLES20.glVertexAttribPointer(aPositionHandle, 3, GLES20.GL_FLOAT, false,
SQUARE_VERTEX_DATA_STRIDE_BYTES, squareVertex);
GLES20.glEnableVertexAttribArray(aPositionHandle);
squareVertex.position(SQUARE_VERTEX_DATA_UV_OFFSET);
GLES20.glVertexAttribPointer(aTextureHandle, 2, GLES20.GL_FLOAT, false,
SQUARE_VERTEX_DATA_STRIDE_BYTES, squareVertex);
GLES20.glEnableVertexAttribArray(aTextureHandle);
squareVertexObject.position(SQUARE_VERTEX_DATA_POS_OFFSET);
GLES20.glVertexAttribPointer(aTextureObjectHandle, 2, GLES20.GL_FLOAT, false,
2 * FLOAT_SIZE_BYTES, squareVertexObject);
GLES20.glEnableVertexAttribArray(aTextureObjectHandle);
GLES20.glUniformMatrix4fv(uMVPMatrixHandle, 1, false, MVPMatrix, 0);
GLES20.glUniformMatrix4fv(uSTMatrixHandle, 1, false, STMatrix, 0);
//Sampler
GLES20.glUniform1i(uSamplerHandle, 4);
GLES20.glActiveTexture(GLES20.GL_TEXTURE4);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, previousTexId);
//Object
GLES20.glUniform1i(uObjectHandle, 0);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, streamObjectTextureId[0]);
GLES20.glUniform1f(uSensitiveHandle, sensitive);
}
@Override
public void release() {
GLES20.glDeleteProgram(program);
releaseTexture();
sprite.reset();
}
private void releaseTexture() {
GLES20.glDeleteTextures(streamObjectTextureId.length, streamObjectTextureId, 0);
streamObjectTextureId = new int[] { -1 };
}
private void setScale(float scaleX, float scaleY) {
sprite.scale(scaleX, scaleY);
squareVertexObject.put(sprite.getTransformedVertices()).position(0);
}
private void setPosition(TranslateTo positionTo) {
sprite.translate(positionTo);
squareVertexObject.put(sprite.getTransformedVertices()).position(0);
}
public void setImage(Bitmap bitmap) {
((ImageStreamObject) streamObject).load(bitmap);
shouldLoad = true;
setScale(100f, 100f);
setPosition(TranslateTo.CENTER);
}
public void setSensitive(float sensitive) {
this.sensitive = sensitive;
}
}

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl.render.filters;
import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl.render.filters;
import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl.render.filters;
import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl.render.filters;
import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl.render.filters;
import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl.render.filters;
import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl.render.filters;
import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl.render.filters;
import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl.render.filters;
import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl.render.filters;
import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl.render.filters;
import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl.render.filters;
import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl.render.filters;
import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl.render.filters;
import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl.render.filters;
import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl.render.filters;
import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl.render.filters;
import android.content.Context;

View File

@ -1,3 +1,19 @@
/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.input.gl.render.filters;
import android.content.Context;

Some files were not shown because too many files have changed in this diff Show More