Alpha version
|
@ -0,0 +1,14 @@
|
|||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
|
@ -0,0 +1,65 @@
|
|||
<?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>
|
|
@ -0,0 +1,3 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
|
@ -0,0 +1 @@
|
|||
Peeriscope
|
|
@ -0,0 +1,139 @@
|
|||
<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>
|
|
@ -0,0 +1,5 @@
|
|||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="11" />
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,8 @@
|
|||
<component name="ProjectDictionaryState">
|
||||
<dictionary name="schoumi">
|
||||
<words>
|
||||
<w>datas</w>
|
||||
<w>rtmp</w>
|
||||
</words>
|
||||
</dictionary>
|
||||
</component>
|
|
@ -0,0 +1,27 @@
|
|||
<?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="PLATFORM" />
|
||||
<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" />
|
||||
<option name="useQualifiedModuleNames" value="true" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,30 @@
|
|||
<?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>
|
|
@ -0,0 +1,14 @@
|
|||
<?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>
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RunConfigurationProducerService">
|
||||
<option name="ignoredProducers">
|
||||
<set>
|
||||
<option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1 @@
|
|||
/build
|
|
@ -0,0 +1,59 @@
|
|||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-parcelize'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'androidx.navigation.safeargs.kotlin'
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
buildToolsVersion "30.0.3"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "fr.mobdev.peertubelive"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 30
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables
|
||||
{
|
||||
useSupportLibrary true
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
dataBinding true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||
implementation project(':rtplibrary')
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
implementation 'androidx.core:core-ktx:1.3.2'
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
implementation 'com.google.android.material:material:1.3.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'
|
||||
testImplementation 'junit:junit:4.13.1'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
|
||||
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"version": 2,
|
||||
"artifactType": {
|
||||
"type": "APK",
|
||||
"kind": "Directory"
|
||||
},
|
||||
"applicationId": "org.framasoft.peertubelive",
|
||||
"variantName": "release",
|
||||
"elements": [
|
||||
{
|
||||
"type": "SINGLE",
|
||||
"filters": [],
|
||||
"versionCode": 1,
|
||||
"versionName": "1.0",
|
||||
"outputFile": "app-release.apk"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package org.framasoft.peertubelive
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("org.framasoft.peeriscope", appContext.packageName)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="fr.mobdev.peertubelive">
|
||||
|
||||
|
||||
<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" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:name="fr.mobdev.peertubelive.activity.MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme">
|
||||
<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/Theme.Design.Light.NoActionBar" android:screenOrientation="portrait"/>
|
||||
<activity android:name="fr.mobdev.peertubelive.activity.CreateLiveActivity" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
After Width: | Height: | Size: 416 KiB |
|
@ -0,0 +1,58 @@
|
|||
package fr.mobdev.peertubelive.activity
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import fr.mobdev.peertubelive.R
|
||||
import fr.mobdev.peertubelive.databinding.InstanceItemBinding
|
||||
import fr.mobdev.peertubelive.objects.OAuthData
|
||||
|
||||
class AccountAdapter(private var accounts: List<OAuthData>): RecyclerView.Adapter<AccountAdapter.ViewHolder>() {
|
||||
|
||||
var onDeleteAccount: OnDeleteAccount? = null
|
||||
var onClickListener: OnClickListener? = null
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val binding = DataBindingUtil.inflate<InstanceItemBinding>(LayoutInflater.from(parent.context), R.layout.instance_item, parent, false)
|
||||
return ViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val oauthData = accounts[position]
|
||||
holder.binding.username.text = oauthData.username
|
||||
holder.binding.url.text = oauthData.baseUrl
|
||||
holder.pos = position
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return accounts.size
|
||||
}
|
||||
|
||||
fun setAccounts(accounts: List<OAuthData>) {
|
||||
this.accounts = accounts
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
inner class ViewHolder(val binding: InstanceItemBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||
var pos: Int = 0
|
||||
init {
|
||||
binding.root.setOnClickListener {
|
||||
onClickListener?.onClick(accounts[pos])
|
||||
}
|
||||
|
||||
binding.root.setOnLongClickListener {
|
||||
onDeleteAccount?.onDeleteAccount(accounts[pos])
|
||||
return@setOnLongClickListener true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface OnDeleteAccount{
|
||||
fun onDeleteAccount(oAuthData: OAuthData)
|
||||
}
|
||||
|
||||
interface OnClickListener{
|
||||
fun onClick(oAuthData: OAuthData)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,373 @@
|
|||
package fr.mobdev.peertubelive.activity
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.ArrayAdapter
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import fr.mobdev.peertubelive.R
|
||||
import fr.mobdev.peertubelive.databinding.ChannelListBinding
|
||||
import fr.mobdev.peertubelive.manager.DatabaseManager
|
||||
import fr.mobdev.peertubelive.manager.InstanceManager
|
||||
import fr.mobdev.peertubelive.objects.ChannelData
|
||||
import fr.mobdev.peertubelive.objects.OAuthData
|
||||
import fr.mobdev.peertubelive.objects.StreamData
|
||||
import fr.mobdev.peertubelive.objects.StreamSettings
|
||||
import fr.mobdev.peertubelive.utils.TranslationUtils
|
||||
import java.util.ArrayList
|
||||
|
||||
class CreateLiveActivity : AppCompatActivity() {
|
||||
|
||||
companion object{
|
||||
const val OAUTH_DATA = "OAUTH_DATA"
|
||||
}
|
||||
|
||||
private lateinit var channels: List<ChannelData>
|
||||
private lateinit var categories: MutableMap<String, Int>
|
||||
private lateinit var licences: MutableMap<String, Int>
|
||||
private lateinit var privacies: MutableMap<String, Int>
|
||||
private lateinit var languages: MutableMap<String, String>
|
||||
private lateinit var oAuthData: OAuthData
|
||||
private var inError: Boolean = false
|
||||
private var showAdvancedSettings = true
|
||||
private lateinit var binding: ChannelListBinding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
oAuthData = intent.getParcelableExtra(OAUTH_DATA)!!
|
||||
|
||||
binding = DataBindingUtil.setContentView(this,R.layout.channel_list)
|
||||
|
||||
binding.error.visibility = View.GONE
|
||||
binding.channelList.visibility = View.GONE
|
||||
binding.liveTitle.visibility = View.GONE
|
||||
binding.channel.visibility = View.GONE
|
||||
binding.title.visibility = View.GONE
|
||||
binding.titleError.visibility = View.GONE
|
||||
binding.advanceSettings.visibility = View.GONE
|
||||
binding.privacy.visibility = View.GONE
|
||||
binding.privacyList.visibility = View.GONE
|
||||
|
||||
toggleAdvanceSettings()
|
||||
|
||||
binding.goLive.isEnabled = false
|
||||
binding.advanceSettings.setOnClickListener{
|
||||
toggleAdvanceSettings()
|
||||
}
|
||||
binding.goLive.setOnClickListener {
|
||||
val title = binding.liveTitle.text.toString()
|
||||
val channel = channels[binding.channelList.selectedItemPosition].id
|
||||
var category: Int? = categories[binding.categoryList.selectedItem.toString()]!!
|
||||
val privacy = privacies[binding.privacyList.selectedItem.toString()]!!
|
||||
if (category == 0)
|
||||
category = null
|
||||
var language: String? = languages[binding.languageList.selectedItem.toString()]!!
|
||||
if (language != null && language.isEmpty())
|
||||
language = null
|
||||
var licence: Int? = licences[binding.licenceList.selectedItem.toString()]!!
|
||||
if (licence == 0)
|
||||
licence = null
|
||||
|
||||
var description: String? = binding.description.text.toString()
|
||||
if (description != null && description.isEmpty())
|
||||
description = null
|
||||
|
||||
val comments = binding.commentsEnabled.isChecked
|
||||
val download = binding.downloadEnabled.isChecked
|
||||
val nsfw = binding.nsfw.isChecked
|
||||
val replay = binding.saveReplay.isChecked
|
||||
|
||||
val streamSettings = StreamSettings(title,channel,privacy,category,language,licence,description,comments,download,nsfw,replay)
|
||||
DatabaseManager.updateStreamSettings(this,streamSettings)
|
||||
if(title.isEmpty())
|
||||
{
|
||||
binding.titleError.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.titleError.visibility = View.GONE
|
||||
goLive(streamSettings)
|
||||
}
|
||||
}
|
||||
|
||||
InstanceManager.getCategoryList(this,oAuthData.baseUrl!!,object : InstanceManager.InstanceListener {
|
||||
override fun onSuccess(args: Bundle?) {
|
||||
val map = args?.getSerializable(InstanceManager.EXTRA_DATA)!! as Map<String, Int>
|
||||
categories = HashMap(map.count())
|
||||
for(pair in map.entries) {
|
||||
val stringId = TranslationUtils.getCategoryTranslationFor(pair.key)
|
||||
if(stringId != -1)
|
||||
categories[getString(stringId)] = pair.value
|
||||
else
|
||||
categories[pair.key] = pair.value
|
||||
}
|
||||
updateView(null)
|
||||
}
|
||||
|
||||
override fun onError(error: String?) {
|
||||
inError = true
|
||||
updateView(error)
|
||||
}
|
||||
|
||||
override fun onUpdateOAuthData(oauthData: OAuthData) {
|
||||
this@CreateLiveActivity.oAuthData.updateData(oauthData)
|
||||
}
|
||||
})
|
||||
|
||||
InstanceManager.getUserChannelList(this,oAuthData.baseUrl!!,oAuthData, object : InstanceManager.InstanceListener {
|
||||
override fun onSuccess(args: Bundle?) {
|
||||
channels = args?.getParcelableArrayList<ChannelData>(InstanceManager.EXTRA_DATA)!!
|
||||
updateView(null)
|
||||
}
|
||||
|
||||
override fun onError(error: String?) {
|
||||
inError = true
|
||||
updateView(error)
|
||||
}
|
||||
|
||||
override fun onUpdateOAuthData(oauthData: OAuthData) {
|
||||
this@CreateLiveActivity.oAuthData.updateData(oauthData)
|
||||
}
|
||||
})
|
||||
|
||||
InstanceManager.getPrivacyList(this,oAuthData.baseUrl!!, object : InstanceManager.InstanceListener {
|
||||
override fun onSuccess(args: Bundle?) {
|
||||
val map = args?.getSerializable(InstanceManager.EXTRA_DATA)!! as Map<String, Int>
|
||||
privacies = HashMap(map.count())
|
||||
for(pair in map.entries) {
|
||||
val stringId = TranslationUtils.getPrivacyTranslationFor(pair.key)
|
||||
if(stringId != -1)
|
||||
privacies[getString(stringId)] = pair.value
|
||||
else
|
||||
privacies[pair.key] = pair.value
|
||||
}
|
||||
updateView(null)
|
||||
}
|
||||
|
||||
override fun onError(error: String?) {
|
||||
inError = true
|
||||
updateView(error)
|
||||
}
|
||||
|
||||
override fun onUpdateOAuthData(oauthData: OAuthData) {
|
||||
this@CreateLiveActivity.oAuthData.updateData(oauthData)
|
||||
}
|
||||
})
|
||||
|
||||
InstanceManager.getLicencesList(this,oAuthData.baseUrl!!, object : InstanceManager.InstanceListener {
|
||||
override fun onSuccess(args: Bundle?) {
|
||||
var map = args?.getSerializable(InstanceManager.EXTRA_DATA)!! as Map<String, Int>
|
||||
licences = HashMap(map.count())
|
||||
for(pair in map.entries) {
|
||||
val stringId = TranslationUtils.getLicenceTranslationFor(pair.key)
|
||||
if(stringId != -1)
|
||||
licences[getString(stringId)] = pair.value
|
||||
else
|
||||
licences[pair.key] = pair.value
|
||||
}
|
||||
updateView(null)
|
||||
}
|
||||
|
||||
override fun onError(error: String?) {
|
||||
inError = true
|
||||
updateView(error)
|
||||
}
|
||||
|
||||
override fun onUpdateOAuthData(oauthData: OAuthData) {
|
||||
this@CreateLiveActivity.oAuthData.updateData(oauthData)
|
||||
}
|
||||
})
|
||||
|
||||
InstanceManager.getLanguageList(this,oAuthData.baseUrl!!, object : InstanceManager.InstanceListener {
|
||||
override fun onSuccess(args: Bundle?) {
|
||||
val map = args?.getSerializable(InstanceManager.EXTRA_DATA)!! as Map<String, String>
|
||||
languages = HashMap(map.count())
|
||||
for(pair in map.entries) {
|
||||
val stringId = TranslationUtils.getLanguageTranslationFor(pair.key)
|
||||
if(stringId != -1)
|
||||
languages[getString(stringId)] = pair.value
|
||||
else
|
||||
languages[pair.key] = pair.value
|
||||
}
|
||||
updateView(null)
|
||||
}
|
||||
|
||||
override fun onError(error: String?) {
|
||||
inError = true
|
||||
updateView(error)
|
||||
}
|
||||
|
||||
override fun onUpdateOAuthData(oauthData: OAuthData) {
|
||||
this@CreateLiveActivity.oAuthData.updateData(oauthData)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun updateView(error: String?) {
|
||||
if(!inError) {
|
||||
if(this::channels.isInitialized && this::categories.isInitialized && this::privacies.isInitialized && this::languages.isInitialized && this::licences.isInitialized) {
|
||||
val channelAdapter = ArrayAdapter<String>(this, android.R.layout.simple_spinner_dropdown_item)
|
||||
for (channel in channels) {
|
||||
channelAdapter.add(channel.name)
|
||||
}
|
||||
|
||||
val categoryAdapter = ArrayAdapter<String>(this, android.R.layout.simple_spinner_dropdown_item)
|
||||
val categoryList = ArrayList(categories.keys)
|
||||
categoryList.sort()
|
||||
categoryAdapter.addAll(categoryList)
|
||||
|
||||
val licencesAdapter = ArrayAdapter<String>(this, android.R.layout.simple_spinner_dropdown_item)
|
||||
licencesAdapter.addAll(licences.keys)
|
||||
licencesAdapter.sort { o1, o2 -> licences[o1]!!.compareTo(licences[o2]!!)}
|
||||
|
||||
val privacyAdapter = ArrayAdapter<String>(this, android.R.layout.simple_spinner_dropdown_item)
|
||||
privacyAdapter.addAll(privacies.keys)
|
||||
privacyAdapter.sort { o1, o2 -> privacies[o1]!!.compareTo(privacies[o2]!!)}
|
||||
|
||||
|
||||
val languageAdapter = ArrayAdapter<String>(this, android.R.layout.simple_spinner_dropdown_item)
|
||||
val languageList = ArrayList(languages.keys)
|
||||
languageList.sort()
|
||||
languageAdapter.addAll(languageList)
|
||||
|
||||
runOnUiThread {
|
||||
binding.goLive.isEnabled = true
|
||||
binding.channelList.adapter = channelAdapter
|
||||
binding.categoryList.adapter = categoryAdapter
|
||||
binding.privacyList.adapter = privacyAdapter
|
||||
binding.licenceList.adapter = licencesAdapter
|
||||
binding.languageList.adapter = languageAdapter
|
||||
binding.loadingProgress.visibility = View.GONE
|
||||
binding.loadingChannels.visibility = View.GONE
|
||||
binding.channelList.visibility = View.VISIBLE
|
||||
binding.liveTitle.visibility = View.VISIBLE
|
||||
binding.channel.visibility = View.VISIBLE
|
||||
binding.title.visibility = View.VISIBLE
|
||||
binding.advanceSettings.visibility = View.VISIBLE
|
||||
binding.privacy.visibility = View.VISIBLE
|
||||
binding.privacyList.visibility = View.VISIBLE
|
||||
restoreSettings()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
runOnUiThread {
|
||||
binding.error.text = error
|
||||
binding.error.visibility = View.VISIBLE
|
||||
binding.loadingProgress.visibility = View.GONE
|
||||
binding.loadingChannels.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun goLive(streamSettings: StreamSettings) {
|
||||
InstanceManager.createLive(this,oAuthData.baseUrl!!,oAuthData,streamSettings,object : InstanceManager.InstanceListener {
|
||||
override fun onSuccess(args: Bundle?) {
|
||||
if(args != null) {
|
||||
val streamData = args.getParcelable<StreamData>(InstanceManager.EXTRA_DATA)!!
|
||||
val intent = Intent(this@CreateLiveActivity, StreamActivity::class.java)
|
||||
intent.putExtra(InstanceManager.EXTRA_DATA,streamData)
|
||||
startActivityForResult(intent,2)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(error: String?) {
|
||||
runOnUiThread {
|
||||
binding.error.text = error
|
||||
binding.error.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
override fun onUpdateOAuthData(oauthData: OAuthData) {
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
private fun toggleAdvanceSettings() {
|
||||
showAdvancedSettings = !showAdvancedSettings
|
||||
var status = View.VISIBLE
|
||||
binding.advanceSettings.setText(R.string.advanced_settings_expand)
|
||||
if (!showAdvancedSettings) {
|
||||
status = View.GONE
|
||||
binding.advanceSettings.setText(R.string.advanced_settings)
|
||||
}
|
||||
|
||||
|
||||
binding.category.visibility = status
|
||||
binding.categoryList.visibility = status
|
||||
binding.licence.visibility = status
|
||||
binding.licenceList.visibility = status
|
||||
binding.language.visibility = status
|
||||
binding.languageList.visibility = status
|
||||
binding.description.visibility = status
|
||||
binding.descriptionTitle.visibility = status
|
||||
binding.commentsEnabled.visibility = status
|
||||
binding.commentsEnabledTitle.visibility = status
|
||||
binding.downloadEnabled.visibility = status
|
||||
binding.downloadEnabledTitle.visibility = status
|
||||
binding.nsfw.visibility = status
|
||||
binding.nsfwTitle.visibility = status
|
||||
binding.saveReplay.visibility = status
|
||||
binding.saveReplayTitle.visibility = status
|
||||
binding.saveReplayInfo.visibility = status
|
||||
}
|
||||
|
||||
private fun restoreSettings() {
|
||||
val settings = DatabaseManager.getStreamSettings(this)
|
||||
if (settings != null) {
|
||||
binding.commentsEnabled.isChecked = settings.comments
|
||||
binding.downloadEnabled.isChecked = settings.download
|
||||
binding.nsfw.isChecked = settings.nsfw
|
||||
binding.saveReplay.isChecked = settings.saveReplay
|
||||
binding.liveTitle.setText(settings.title)
|
||||
|
||||
if (settings.privacy != 0) {
|
||||
for (privacyIdx in 0..privacies.count()) {
|
||||
val privacy = binding.privacyList.getItemAtPosition(privacyIdx)
|
||||
if (privacies[privacy] == settings.privacy) {
|
||||
binding.privacyList.setSelection(privacyIdx)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
binding.privacyList.setSelection(settings.privacy -1)
|
||||
|
||||
if (settings.licence != 0 && settings.licence != null) {
|
||||
for (licenceIdx in 0..licences.count()) {
|
||||
val licence = binding.licenceList.getItemAtPosition(licenceIdx)
|
||||
if (licences[licence] == settings.licence) {
|
||||
binding.licenceList.setSelection(licenceIdx)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(settings.category != 0) {
|
||||
for (categoryIdx in 0..categories.count()) {
|
||||
val category = binding.categoryList.getItemAtPosition(categoryIdx)
|
||||
if (categories[category] == settings.category) {
|
||||
binding.categoryList.setSelection(categoryIdx)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if (settings.language != null) {
|
||||
for (languageIdx in 0..languages.count()) {
|
||||
val language = binding.languageList.getItemAtPosition(languageIdx)
|
||||
if (languages[language].equals(settings.language)) {
|
||||
binding.languageList.setSelection(languageIdx)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
setResult(resultCode)
|
||||
finish()
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
package fr.mobdev.peertubelive.activity
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import fr.mobdev.peertubelive.R
|
||||
import fr.mobdev.peertubelive.databinding.HomeBinding
|
||||
import fr.mobdev.peertubelive.dialog.AddInstanceDialog
|
||||
import fr.mobdev.peertubelive.manager.DatabaseManager
|
||||
import fr.mobdev.peertubelive.objects.OAuthData
|
||||
import java.util.*
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var accounts : List<OAuthData>
|
||||
private lateinit var adapter: AccountAdapter
|
||||
private lateinit var binding: HomeBinding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = DataBindingUtil.setContentView(this, R.layout.home)
|
||||
binding.instanceList.layoutManager = LinearLayoutManager(binding.root.context)
|
||||
setupList()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
setupList()
|
||||
}
|
||||
|
||||
private fun setupList() {
|
||||
accounts = DatabaseManager.getCredentials(binding.root.context)
|
||||
if (accounts.isNotEmpty()) {
|
||||
if (!this::adapter.isInitialized) {
|
||||
adapter = AccountAdapter(accounts)
|
||||
adapter.onDeleteAccount = object: AccountAdapter.OnDeleteAccount {
|
||||
override fun onDeleteAccount(oAuthData: OAuthData) {
|
||||
val builder = AlertDialog.Builder(this@MainActivity)
|
||||
builder.setMessage(getString(R.string.delete_account, oAuthData.username, oAuthData.baseUrl))
|
||||
builder.setTitle(R.string.delete_account_title)
|
||||
builder.setPositiveButton(R.string.yes){ _: DialogInterface, _: Int ->
|
||||
DatabaseManager.deleteAccount(this@MainActivity, oAuthData)
|
||||
setupList()
|
||||
}
|
||||
builder.setNegativeButton(R.string.no){ _: DialogInterface, _: Int ->
|
||||
//Do Nothing
|
||||
}
|
||||
builder.show()
|
||||
}
|
||||
|
||||
};
|
||||
adapter.onClickListener = object: AccountAdapter.OnClickListener {
|
||||
|
||||
|
||||
override fun onClick(oAuthData: OAuthData) {
|
||||
if (oAuthData.refreshTokenExpires < Calendar.getInstance().timeInMillis) {
|
||||
addOrUpdateAccount(oAuthData)
|
||||
} else {
|
||||
goLive(oAuthData)
|
||||
}
|
||||
}
|
||||
}
|
||||
binding.instanceList.adapter = adapter
|
||||
} else {
|
||||
adapter.setAccounts(accounts)
|
||||
}
|
||||
binding.noInstance.visibility = View.GONE
|
||||
binding.instanceList.visibility = View.VISIBLE
|
||||
}
|
||||
else {
|
||||
binding.noInstance.visibility = View.VISIBLE
|
||||
binding.instanceList.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
private fun goLive(oAuthData: OAuthData) {
|
||||
val intent = Intent(this, CreateLiveActivity::class.java)
|
||||
intent.putExtra(CreateLiveActivity.OAUTH_DATA, oAuthData)
|
||||
startActivityForResult(intent,1)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
super.onCreateOptionsMenu(menu)
|
||||
menuInflater.inflate(R.menu.home, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if(item.itemId == R.id.add_instance) {
|
||||
addOrUpdateAccount(null)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun addOrUpdateAccount(shouldUpdateOAuthData: OAuthData?) {
|
||||
val wizard = AddInstanceDialog()
|
||||
if(shouldUpdateOAuthData != null)
|
||||
wizard.setOauthData(shouldUpdateOAuthData)
|
||||
wizard.setOnAddInstanceListener(object : AddInstanceDialog.OnAddInstanceListener {
|
||||
override fun addSuccess(oAuthData: OAuthData) {
|
||||
runOnUiThread {
|
||||
setupList()
|
||||
if (shouldUpdateOAuthData != null) {
|
||||
goLive(oAuthData)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
wizard.show(supportFragmentManager, "Wizard")
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
val alertBuilder = AlertDialog.Builder(this)
|
||||
alertBuilder.setTitle(R.string.stream_ended)
|
||||
when (resultCode) {
|
||||
StreamActivity.BACKGROUND -> {
|
||||
alertBuilder.setMessage(R.string.background_reason)
|
||||
}
|
||||
StreamActivity.BACK -> {
|
||||
alertBuilder.setMessage(R.string.back_reason)
|
||||
}
|
||||
StreamActivity.LOCK -> {
|
||||
alertBuilder.setMessage(R.string.lock_reason)
|
||||
}
|
||||
}
|
||||
alertBuilder.setPositiveButton(android.R.string.ok,null)
|
||||
alertBuilder.show()
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,336 @@
|
|||
package fr.mobdev.peertubelive.activity
|
||||
|
||||
import android.Manifest.permission
|
||||
import android.content.*
|
||||
import android.content.pm.PackageManager
|
||||
import android.hardware.SensorManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.view.OrientationEventListener
|
||||
import android.view.SurfaceHolder
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import com.pedro.encoder.input.video.CameraHelper
|
||||
import com.pedro.rtplibrary.rtmp.RtmpCamera1
|
||||
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
|
||||
|
||||
class StreamActivity : AppCompatActivity() {
|
||||
|
||||
|
||||
private lateinit var binding: StreamBinding
|
||||
private lateinit var rtmpCamera1 : RtmpCamera1
|
||||
private lateinit var streamData: StreamData
|
||||
private lateinit var orientationEventListener: OrientationEventListener
|
||||
private lateinit var lockReceiver: BroadcastReceiver
|
||||
private var surfaceInit: Boolean = false
|
||||
private var permissionGiven: Boolean = false
|
||||
private var streamIsActive: Boolean = false
|
||||
private var screenOrientation: Int = 0
|
||||
private var lastScreenOrientation: Int = 0
|
||||
private var orientationCounter: Int = 0
|
||||
private var rotationIsLanternEnabled: Boolean = true
|
||||
|
||||
companion object {
|
||||
const val BACKGROUND :Int = 1
|
||||
const val LOCK :Int = 2
|
||||
const val BACK :Int = 3
|
||||
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
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
|
||||
rtmpCamera1.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()
|
||||
}
|
||||
}
|
||||
}
|
||||
orientationEventListener.enable()
|
||||
|
||||
streamData = intent.getParcelableExtra<StreamData>(EXTRA_DATA)!!
|
||||
binding.switchCamera.setOnClickListener { rtmpCamera1.switchCamera() }
|
||||
binding.muteMicro.setOnClickListener {
|
||||
if (rtmpCamera1.isAudioMuted) {
|
||||
rtmpCamera1.enableAudio()
|
||||
binding.muteMicro.setImageResource(R.drawable.baseline_volume_up_24)
|
||||
}
|
||||
else {
|
||||
rtmpCamera1.disableAudio()
|
||||
binding.muteMicro.setImageResource(R.drawable.baseline_volume_off_24)
|
||||
}
|
||||
}
|
||||
binding.flash.setOnClickListener {
|
||||
if (rtmpCamera1.isLanternEnabled) {
|
||||
rtmpCamera1.disableLantern()
|
||||
binding.flash.setImageResource(R.drawable.baseline_flash_off_24)
|
||||
}
|
||||
else {
|
||||
rtmpCamera1.enableLantern()
|
||||
binding.flash.setImageResource(R.drawable.baseline_flash_on_24)
|
||||
}
|
||||
}
|
||||
|
||||
binding.rotation.setOnClickListener {
|
||||
if (rotationIsLanternEnabled) {
|
||||
rotationIsLanternEnabled = !rotationIsLanternEnabled
|
||||
binding.rotation.setImageResource(R.drawable.baseline_screen_lock_rotation_24)
|
||||
}
|
||||
else {
|
||||
rotationIsLanternEnabled = !rotationIsLanternEnabled
|
||||
binding.rotation.setImageResource(R.drawable.baseline_screen_rotation_24)
|
||||
}
|
||||
}
|
||||
binding.surfaceView.holder.addCallback(object: SurfaceHolder.Callback {
|
||||
override fun surfaceCreated(p0: SurfaceHolder) {
|
||||
surfaceInit = true
|
||||
if (permissionGiven)
|
||||
startStream()
|
||||
}
|
||||
|
||||
override fun surfaceChanged(p0: SurfaceHolder, p1: Int, p2: Int, p3: Int) {
|
||||
|
||||
}
|
||||
|
||||
override fun surfaceDestroyed(p0: SurfaceHolder) {
|
||||
if(rtmpCamera1.isStreaming) {
|
||||
rtmpCamera1.stopStream()
|
||||
}
|
||||
rtmpCamera1.stopPreview()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
)
|
||||
|
||||
lockReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
if (intent?.action.equals(Intent.ACTION_SCREEN_OFF)){
|
||||
setResult(LOCK)
|
||||
finish()
|
||||
} else if (intent?.action.equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)){
|
||||
val reason = intent?.getStringExtra("reason")
|
||||
if(reason.equals("homekey")){
|
||||
setResult(BACKGROUND)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val filter = IntentFilter(Intent.ACTION_SCREEN_OFF)
|
||||
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)
|
||||
registerReceiver(lockReceiver, filter)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
val permissions = getUnAllowedPermissions()
|
||||
|
||||
if(permissions.isNotEmpty()) {
|
||||
var shouldShowRequest = true
|
||||
for(perm in permissions){
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
|
||||
shouldShowRequest = shouldShowRequest && shouldShowRequestPermissionRationale(perm)
|
||||
}
|
||||
|
||||
if(shouldShowRequest) {
|
||||
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)
|
||||
startActivity(intent)
|
||||
}
|
||||
} else {
|
||||
ActivityCompat.requestPermissions(this, permissions.toTypedArray(), 1)
|
||||
}
|
||||
} else {
|
||||
permissionGiven = true
|
||||
if(surfaceInit && !streamIsActive)
|
||||
startStream()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
if (!hasWindowFocus()) {
|
||||
unregisterReceiver(lockReceiver)
|
||||
setResult(BACKGROUND)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
val alertBuilder = AlertDialog.Builder(this)
|
||||
alertBuilder.setTitle(R.string.end_stream)
|
||||
alertBuilder.setMessage(R.string.ask_end_stream)
|
||||
alertBuilder.setPositiveButton(R.string.yes) { _: DialogInterface, _: Int ->
|
||||
setResult(BACK)
|
||||
finish()
|
||||
}
|
||||
alertBuilder.setNegativeButton(R.string.no,null)
|
||||
alertBuilder.show()
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
var allPermissionGranted = true
|
||||
for (result in grantResults) {
|
||||
allPermissionGranted = allPermissionGranted && (result == PackageManager.PERMISSION_GRANTED)
|
||||
}
|
||||
|
||||
if (allPermissionGranted) {
|
||||
permissionGiven = true
|
||||
if(surfaceInit && !streamIsActive)
|
||||
startStream()
|
||||
} else {
|
||||
binding.permissionInfo.visibility = View.VISIBLE
|
||||
binding.gotoPermission.visibility = View.VISIBLE
|
||||
binding.surfaceView.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
private fun startStream() {
|
||||
streamIsActive = true
|
||||
binding.permissionInfo.visibility = View.GONE
|
||||
binding.gotoPermission.visibility = View.GONE
|
||||
binding.surfaceView.visibility = View.VISIBLE
|
||||
|
||||
val connectChecker : ConnectCheckerRtmp = object : ConnectCheckerRtmp {
|
||||
override fun onConnectionSuccessRtmp() {
|
||||
runOnUiThread {
|
||||
Toast.makeText(binding.root.context, "Connection success", Toast.LENGTH_SHORT).show();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override fun onConnectionFailedRtmp(reason: String) {
|
||||
runOnUiThread {
|
||||
Toast.makeText(binding.root.context, "Connection failed", Toast.LENGTH_SHORT).show();
|
||||
rtmpCamera1.stopStream()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewBitrateRtmp(bitrate: Long) {
|
||||
|
||||
}
|
||||
|
||||
override fun onDisconnectRtmp() {
|
||||
runOnUiThread {
|
||||
Toast.makeText(binding.root.context, "Disconnect", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onAuthErrorRtmp() {
|
||||
runOnUiThread {
|
||||
Toast.makeText(binding.root.context, "Auth Error", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onAuthSuccessRtmp() {
|
||||
runOnUiThread {
|
||||
Toast.makeText(binding.root.context, "Auth Success", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rtmpCamera1 = RtmpCamera1(binding.surfaceView, connectChecker)
|
||||
|
||||
var resolutions = rtmpCamera1.resolutionsBack
|
||||
var width: Int
|
||||
var height: Int
|
||||
if(resolutions[0].width > resolutions[0].height) {
|
||||
width = resolutions[0].width
|
||||
height = resolutions[0].height
|
||||
} else {
|
||||
width = resolutions[0].height
|
||||
height = resolutions[0].width
|
||||
}
|
||||
for(res in resolutions) {
|
||||
if (width * height < res.width * res.width) {
|
||||
if(res.width > res.height) {
|
||||
width = res.width
|
||||
height = res.height
|
||||
} else {
|
||||
width = res.height
|
||||
height = res.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rtmpCamera1.startPreview(width,height)
|
||||
//start stream
|
||||
|
||||
if (rtmpCamera1.prepareAudio() && rtmpCamera1.prepareVideo(width,height,30,3000*1024,false,CameraHelper.getCameraOrientation(this))) {
|
||||
rtmpCamera1.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) */
|
||||
}
|
||||
}
|
||||
|
||||
private fun getUnAllowedPermissions(): List<String> {
|
||||
val permissions: ArrayList<String> = ArrayList<String>()
|
||||
|
||||
if (ContextCompat.checkSelfPermission(this, permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
|
||||
permissions.add(permission.CAMERA)
|
||||
}
|
||||
if (ContextCompat.checkSelfPermission(this, permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
|
||||
permissions.add(permission.RECORD_AUDIO)
|
||||
}
|
||||
return permissions
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,168 @@
|
|||
package fr.mobdev.peertubelive.dialog
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import fr.mobdev.peertubelive.R
|
||||
import fr.mobdev.peertubelive.databinding.AddInstanceBinding
|
||||
import fr.mobdev.peertubelive.manager.DatabaseManager
|
||||
import fr.mobdev.peertubelive.manager.InstanceManager
|
||||
import fr.mobdev.peertubelive.objects.OAuthData
|
||||
import java.net.MalformedURLException
|
||||
import java.net.URL
|
||||
|
||||
class AddInstanceDialog : DialogFragment() {
|
||||
|
||||
private var onAddInstanceListener: OnAddInstanceListener? = null
|
||||
private lateinit var oAuthData: OAuthData
|
||||
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val binding = DataBindingUtil.inflate<AddInstanceBinding>(LayoutInflater.from(requireContext()), R.layout.add_instance,null,false)
|
||||
|
||||
val builder = AlertDialog.Builder(requireContext())
|
||||
|
||||
builder.setTitle(R.string.add_instance)
|
||||
builder.setPositiveButton(R.string.connect, null)
|
||||
builder.setNegativeButton(R.string.cancel) { dialog,_ -> dialog.dismiss() }
|
||||
builder.setView(binding.root)
|
||||
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
|
||||
|
||||
if (this::oAuthData.isInitialized)
|
||||
{
|
||||
binding.username.isEnabled = false
|
||||
binding.instance.isEnabled = false
|
||||
binding.instance.setText(oAuthData.baseUrl)
|
||||
binding.username.setText(oAuthData.username)
|
||||
}
|
||||
|
||||
|
||||
val dialog = builder.create()
|
||||
dialog.setOnShowListener {
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
|
||||
val username = binding.username.text.toString()
|
||||
val password = binding.password.text.toString()
|
||||
var instance = binding.instance.text.toString()
|
||||
binding.errorUsername.visibility = View.GONE
|
||||
binding.errorInstance.visibility = View.GONE
|
||||
binding.errorPassword.visibility = View.GONE
|
||||
binding.error.visibility = View.GONE
|
||||
var inError = false
|
||||
if(username.isEmpty())
|
||||
{
|
||||
binding.errorUsername.visibility = View.VISIBLE
|
||||
inError = true
|
||||
}
|
||||
if(password.isEmpty())
|
||||
{
|
||||
binding.errorPassword.visibility = View.VISIBLE
|
||||
inError = true
|
||||
}
|
||||
if(instance.isEmpty())
|
||||
{
|
||||
binding.errorInstance.visibility = View.VISIBLE
|
||||
binding.errorInstance.setText(R.string.instance_error)
|
||||
inError = true
|
||||
} else {
|
||||
if(!instance.startsWith("https://"))
|
||||
instance = "https://$instance"
|
||||
if (instance.endsWith("/"))
|
||||
instance = instance.removeRange(instance.length-1,instance.length)
|
||||
try {
|
||||
URL(instance)
|
||||
} catch (e: MalformedURLException) {
|
||||
binding.errorInstance.visibility = View.VISIBLE
|
||||
binding.errorInstance.setText(R.string.malformed_instance_error)
|
||||
inError = true
|
||||
}
|
||||
}
|
||||
if (DatabaseManager.existsCredential(requireContext(),instance,username)) {
|
||||
inError = true
|
||||
binding.error.visibility = View.VISIBLE
|
||||
binding.error.text = requireContext().getString(R.string.account_exist)
|
||||
}
|
||||
if(!inError) {
|
||||
binding.errorUsername.visibility = View.GONE
|
||||
binding.errorInstance.visibility = View.GONE
|
||||
binding.errorPassword.visibility = View.GONE
|
||||
binding.error.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.tryConnect.visibility = View.VISIBLE
|
||||
binding.tryConnectMsg.visibility = View.VISIBLE
|
||||
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false
|
||||
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).isEnabled = false
|
||||
val listener = object : InstanceManager.InstanceListener {
|
||||
override fun onSuccess(args: Bundle?) {
|
||||
val oauthData: OAuthData? = args?.getParcelable(InstanceManager.EXTRA_DATA)
|
||||
if (oauthData != null) {
|
||||
if (this@AddInstanceDialog::oAuthData.isInitialized) {
|
||||
DatabaseManager.updateCredentials(requireContext(), oauthData)
|
||||
} else {
|
||||
DatabaseManager.addNewCredentials(requireContext(), oauthData)
|
||||
}
|
||||
onAddInstanceListener?.addSuccess(oauthData)
|
||||
dialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(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
|
||||
}
|
||||
}
|
||||
|
||||
override fun onUpdateOAuthData(oauthData: OAuthData) {
|
||||
DatabaseManager.updateCredentials(requireContext(), oauthData)
|
||||
}
|
||||
|
||||
};
|
||||
if (this::oAuthData.isInitialized) {
|
||||
InstanceManager.getUserToken(requireContext(), instance, username, password, oAuthData, listener)
|
||||
} else {
|
||||
InstanceManager.registerAccount(requireContext(), instance, username, password,listener)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return dialog
|
||||
}
|
||||
|
||||
fun setOnAddInstanceListener(listener: OnAddInstanceListener) {
|
||||
onAddInstanceListener = listener
|
||||
}
|
||||
|
||||
fun setOauthData(oAuthData: OAuthData) {
|
||||
this.oAuthData = oAuthData
|
||||
}
|
||||
|
||||
interface OnAddInstanceListener {
|
||||
fun addSuccess(oAuthData: OAuthData)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package fr.mobdev.peertubelive.manager
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.database.sqlite.SQLiteOpenHelper
|
||||
|
||||
class DatabaseHelper(context: Context) : SQLiteOpenHelper(context, DB_NAME, null, 2) {
|
||||
|
||||
companion object {
|
||||
const val DB_NAME: String = "peeriscope.db"
|
||||
const val TABLE_CREDS: String = "Credentials"
|
||||
const val CREDS_USERNAME: String = "Username"
|
||||
const val CREDS_BASE_URL: String = "Instance"
|
||||
const val CREDS_CLIENT_ID: String = "ClientID"
|
||||
const val CREDS_CLIENT_SECRET: String = "ClientSecret"
|
||||
const val CREDS_ACCESS_TOKEN: String = "AccessToken"
|
||||
const val CREDS_TOKEN_TYPE: String = "TokenType"
|
||||
const val CREDS_EXPIRES: String = "Expires"
|
||||
const val CREDS_REFRESH_TOKEN: String = "RefreshToken"
|
||||
const val CREDS_REFRESH_EXPIRES: String = "RefreshTokenExpires"
|
||||
const val TABLE_STREAM_SETTINGS: String = "Settings"
|
||||
const val SETS_TITLE: String = "Title"
|
||||
const val SETS_CATEGORY: String = "Category"
|
||||
const val SETS_PRIVACY : String = "Privacy"
|
||||
const val SETS_LANGUAGE : String = "Language"
|
||||
const val SETS_LICENCE : String = "Licence"
|
||||
const val SETS_COMMENTS : String = "Comments"
|
||||
const val SETS_DOWNLOAD : String = "Download"
|
||||
const val SETS_REPLAY : String = "Replay"
|
||||
const val SETS_NSFW : String = "Nsfw"
|
||||
}
|
||||
|
||||
override fun onCreate(db: SQLiteDatabase?) {
|
||||
db?.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_CREDS (id INTEGER PRIMARY KEY, $CREDS_USERNAME TEXT, $CREDS_BASE_URL TEXT, " +
|
||||
"$CREDS_CLIENT_ID TEXT, $CREDS_CLIENT_SECRET TEXT, $CREDS_ACCESS_TOKEN TEXT, " +
|
||||
"$CREDS_TOKEN_TYPE TEXT, $CREDS_EXPIRES INTEGER, $CREDS_REFRESH_TOKEN TEXT, $CREDS_REFRESH_EXPIRES INTEGER);")
|
||||
|
||||
db?.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_STREAM_SETTINGS (id INTEGER PRIMARY KEY, $SETS_TITLE TEXT, $SETS_CATEGORY INTEGER, $SETS_PRIVACY INTEGER, " +
|
||||
"$SETS_LANGUAGE TEXT, $SETS_LICENCE INTEGER, $SETS_COMMENTS INTEGER, " +
|
||||
"$SETS_DOWNLOAD INTEGER, $SETS_REPLAY INTEGER, $SETS_NSFW INTEGER);")
|
||||
|
||||
val values = ContentValues()
|
||||
values.put("id",1)
|
||||
values.put(SETS_TITLE,"Live")
|
||||
values.put(SETS_COMMENTS,true)
|
||||
values.put(SETS_DOWNLOAD,true)
|
||||
values.put(SETS_NSFW,false)
|
||||
values.put(SETS_REPLAY,false)
|
||||
db?.insert(TABLE_STREAM_SETTINGS,null,values)
|
||||
}
|
||||
|
||||
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
|
||||
db?.execSQL("ALTER TABLE $TABLE_CREDS add column $CREDS_REFRESH_EXPIRES INTEGER;")
|
||||
}
|
||||
|
||||
fun insert(table: String, values: ContentValues): Long {
|
||||
return writableDatabase.insert(table,null,values)
|
||||
}
|
||||
|
||||
fun update(table: String, values: ContentValues, whereClause: String?, whereArgs: Array<String?>): Int {
|
||||
return writableDatabase.update(table,values,whereClause,whereArgs)
|
||||
}
|
||||
|
||||
fun delete(table: String, whereClause: String?, whereArgs: Array<String?>?): Int {
|
||||
return writableDatabase.delete(table,whereClause,whereArgs)
|
||||
}
|
||||
|
||||
fun query(table: String, columns: Array<String>?, whereClause: String?, whereArgs: Array<String>?): Cursor {
|
||||
return readableDatabase.query(table,columns,whereClause,whereArgs,null,null,null)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
package fr.mobdev.peertubelive.manager
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import fr.mobdev.peertubelive.objects.OAuthData
|
||||
import fr.mobdev.peertubelive.objects.StreamSettings
|
||||
|
||||
object DatabaseManager {
|
||||
private var databaseHelper: DatabaseHelper? = null
|
||||
|
||||
|
||||
fun addNewCredentials(context: Context, oAuthData: OAuthData) {
|
||||
if (databaseHelper == null)
|
||||
databaseHelper = DatabaseHelper(context)
|
||||
|
||||
val values = ContentValues()
|
||||
values.put(DatabaseHelper.CREDS_BASE_URL,oAuthData.baseUrl)
|
||||
values.put(DatabaseHelper.CREDS_USERNAME,oAuthData.username)
|
||||
values.put(DatabaseHelper.CREDS_CLIENT_ID,oAuthData.clientId)
|
||||
values.put(DatabaseHelper.CREDS_CLIENT_SECRET,oAuthData.clientSecret)
|
||||
values.put(DatabaseHelper.CREDS_ACCESS_TOKEN,oAuthData.accessToken)
|
||||
values.put(DatabaseHelper.CREDS_TOKEN_TYPE,oAuthData.tokenType)
|
||||
values.put(DatabaseHelper.CREDS_EXPIRES,oAuthData.expires)
|
||||
values.put(DatabaseHelper.CREDS_REFRESH_TOKEN,oAuthData.refreshToken)
|
||||
values.put(DatabaseHelper.CREDS_REFRESH_EXPIRES,oAuthData.refreshTokenExpires)
|
||||
databaseHelper?.insert(DatabaseHelper.TABLE_CREDS,values)
|
||||
|
||||
}
|
||||
|
||||
fun updateCredentials(context: Context, oAuthData: OAuthData) {
|
||||
if (databaseHelper == null)
|
||||
databaseHelper = DatabaseHelper(context)
|
||||
|
||||
val values = ContentValues()
|
||||
val whereClause = "${DatabaseHelper.CREDS_USERNAME} = ? AND ${DatabaseHelper.CREDS_BASE_URL} = ? AND ${DatabaseHelper.CREDS_CLIENT_SECRET} = ?"
|
||||
val whereArgs = arrayOf(oAuthData.username , oAuthData.baseUrl , oAuthData.clientSecret)
|
||||
values.put(DatabaseHelper.CREDS_ACCESS_TOKEN,oAuthData.accessToken)
|
||||
values.put(DatabaseHelper.CREDS_TOKEN_TYPE,oAuthData.tokenType)
|
||||
values.put(DatabaseHelper.CREDS_EXPIRES,oAuthData.expires)
|
||||
values.put(DatabaseHelper.CREDS_REFRESH_TOKEN,oAuthData.refreshToken)
|
||||
values.put(DatabaseHelper.CREDS_REFRESH_EXPIRES,oAuthData.refreshTokenExpires)
|
||||
databaseHelper?.update(DatabaseHelper.TABLE_CREDS,values,whereClause,whereArgs)
|
||||
}
|
||||
|
||||
fun getCredentials(context: Context): List<OAuthData> {
|
||||
if (databaseHelper == null)
|
||||
databaseHelper = DatabaseHelper(context)
|
||||
val oAuthDatas: ArrayList<OAuthData> = ArrayList()
|
||||
val cursor: Cursor? = databaseHelper?.query(DatabaseHelper.TABLE_CREDS,null,null,null)
|
||||
while (cursor?.moveToNext() == true) {
|
||||
var col = 1
|
||||
val username: String = cursor.getString(col++)
|
||||
val baseUrl: String = cursor.getString(col++)
|
||||
val clientId: String = cursor.getString(col++)
|
||||
val clientSecret: String = cursor.getString(col++)
|
||||
val accessToken: String = cursor.getString(col++)
|
||||
val tokenType: String = cursor.getString(col++)
|
||||
val expires: Long = cursor.getLong(col++)
|
||||
val refreshToken: String = cursor.getString(col++)
|
||||
val refreshTokenExpires: Long = cursor.getLong(col)
|
||||
var oAuthData = OAuthData(baseUrl,username,clientId,clientSecret,accessToken,tokenType,expires,refreshToken,refreshTokenExpires)
|
||||
oAuthDatas.add(oAuthData)
|
||||
|
||||
}
|
||||
cursor?.close()
|
||||
return oAuthDatas
|
||||
}
|
||||
|
||||
fun existsCredential(context: Context, url: String, username: String): Boolean {
|
||||
if (databaseHelper == null)
|
||||
databaseHelper = DatabaseHelper(context)
|
||||
val columns = arrayOf(DatabaseHelper.CREDS_BASE_URL, DatabaseHelper.CREDS_USERNAME)
|
||||
val whereClause = "${DatabaseHelper.CREDS_USERNAME} = ? AND ${DatabaseHelper.CREDS_BASE_URL} = ?"
|
||||
val whereArgs = arrayOf(username, url)
|
||||
val cursor: Cursor? = databaseHelper?.query(DatabaseHelper.TABLE_CREDS,columns,whereClause,whereArgs)
|
||||
val exist = cursor?.count != 0
|
||||
cursor?.close()
|
||||
return exist
|
||||
}
|
||||
|
||||
fun deleteAccount(context: Context, oAuthData: OAuthData) {
|
||||
if (databaseHelper == null)
|
||||
databaseHelper = DatabaseHelper(context)
|
||||
val whereClause = "${DatabaseHelper.CREDS_USERNAME} = ? AND ${DatabaseHelper.CREDS_BASE_URL} = ? AND ${DatabaseHelper.CREDS_CLIENT_ID} = ?"
|
||||
val whereArgs = arrayOf(oAuthData.username, oAuthData.baseUrl, oAuthData.clientId)
|
||||
databaseHelper?.delete(DatabaseHelper.TABLE_CREDS,whereClause,whereArgs)
|
||||
}
|
||||
|
||||
fun getStreamSettings(context: Context): StreamSettings? {
|
||||
if (databaseHelper == null)
|
||||
databaseHelper = DatabaseHelper(context)
|
||||
|
||||
val cursor: Cursor? = databaseHelper?.query(DatabaseHelper.TABLE_STREAM_SETTINGS,null,null,null)
|
||||
var streamSettings: StreamSettings? = null
|
||||
if(cursor?.moveToNext() == true) {
|
||||
var col = 1
|
||||
val title: String = cursor.getString(col++)
|
||||
val category: Int = cursor.getInt(col++)
|
||||
val privacy: Int = cursor.getInt(col++)
|
||||
val language: String? = cursor.getString(col++)
|
||||
val licence: Int = cursor.getInt(col++)
|
||||
val comments: Boolean = cursor.getInt(col++) == 1
|
||||
val download: Boolean = cursor.getInt(col++) == 1
|
||||
val saveReplay: Boolean = cursor.getInt(col++) == 1
|
||||
val nsfw: Boolean = cursor.getInt(col) == 1
|
||||
streamSettings = StreamSettings(title,0,privacy,category,language,licence,null,comments,download,nsfw,saveReplay)
|
||||
}
|
||||
cursor?.close()
|
||||
return streamSettings
|
||||
}
|
||||
|
||||
fun updateStreamSettings(context: Context, streamSettings: StreamSettings) {
|
||||
if (databaseHelper == null)
|
||||
databaseHelper = DatabaseHelper(context)
|
||||
val values = ContentValues()
|
||||
values.put(DatabaseHelper.SETS_TITLE,streamSettings.title)
|
||||
values.put(DatabaseHelper.SETS_PRIVACY,streamSettings.privacy)
|
||||
values.put(DatabaseHelper.SETS_CATEGORY,streamSettings.category)
|
||||
values.put(DatabaseHelper.SETS_COMMENTS,streamSettings.comments)
|
||||
values.put(DatabaseHelper.SETS_DOWNLOAD,streamSettings.download)
|
||||
values.put(DatabaseHelper.SETS_NSFW,streamSettings.nsfw)
|
||||
values.put(DatabaseHelper.SETS_REPLAY,streamSettings.saveReplay)
|
||||
values.put(DatabaseHelper.SETS_LANGUAGE,streamSettings.language)
|
||||
values.put(DatabaseHelper.SETS_LICENCE,streamSettings.licence)
|
||||
|
||||
|
||||
val whereClause = "id = ?"
|
||||
val whereArgs: Array<String?> = arrayOf("1")
|
||||
databaseHelper?.update(DatabaseHelper.TABLE_STREAM_SETTINGS,values,whereClause,whereArgs)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,473 @@
|
|||
package fr.mobdev.peertubelive.manager
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import fr.mobdev.peertubelive.R
|
||||
import fr.mobdev.peertubelive.objects.ChannelData
|
||||
import fr.mobdev.peertubelive.objects.OAuthData
|
||||
import fr.mobdev.peertubelive.objects.StreamData
|
||||
import fr.mobdev.peertubelive.objects.StreamSettings
|
||||
import org.json.JSONObject
|
||||
import java.lang.Exception
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
object InstanceManager {
|
||||
private val oauthManager : OAuthManager = OAuthManager()
|
||||
|
||||
private const val BASE_API_ENDPOINT: String = "/api/v1"
|
||||
private const val REGISTER_CLIENT_ENDPOINT: String = "/oauth-clients/local"
|
||||
private const val GET_USER_CLIENT_ENDPOINT: String = "/users/token"
|
||||
private const val GET_USER_INFO_ENDPOINT: String = "/users/me"
|
||||
private const val CREATE_LIVE_ENDPOINT: String = "/videos/live"
|
||||
private const val GET_CATEGORY_ENDPOINT: String = "/videos/categories"
|
||||
private const val GET_PRIVACY_ENDPOINT: String = "/videos/privacies"
|
||||
private const val GET_LICENCE_ENDPOINT: String = "/videos/licences"
|
||||
private const val GET_LANGUAGES_ENDPOINT: String = "/videos/languages"
|
||||
private const val GET_VIDEOS: String = "/users/me/videos"
|
||||
|
||||
internal const val EXTRA_DATA: String = "EXTRA_DATA"
|
||||
private const val CONTENT_TYPE: String = "CONTENT_TYPE"
|
||||
private const val CONTENT_DATA: String = "CONTENT_DATA"
|
||||
|
||||
private const val VIDEO_CHANNEL: String = "videoChannels"
|
||||
private const val CHANNEL_ID: String = "id"
|
||||
private const val CHANNEL_NAME: String = "displayName"
|
||||
private const val VIDEO: String = "video"
|
||||
private const val UUID: String = "uuid"
|
||||
private const val RTMP_URL: String = "rtmpUrl"
|
||||
private const val STREAM_KEY: String = "streamKey"
|
||||
|
||||
fun registerAccount(context: Context, url: String, username: String, password: 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)
|
||||
else
|
||||
listener.onError(context.getString(R.string.unknwon_error))
|
||||
}
|
||||
|
||||
override fun onError(error: String?) {
|
||||
listener.onError(error)
|
||||
}
|
||||
|
||||
override fun onUpdateOAuthData(oauthData: OAuthData) {
|
||||
listener.onUpdateOAuthData(oauthData)
|
||||
}
|
||||
|
||||
}
|
||||
oauthManager.register(context,registerUrl,internalListener)
|
||||
}
|
||||
|
||||
fun getUserToken(context: Context, url: String, username: String, password: String, oauthData: OAuthData, listener: InstanceListener) {
|
||||
val userAccess = url + BASE_API_ENDPOINT + GET_USER_CLIENT_ENDPOINT
|
||||
oauthManager.getUserToken(context, userAccess, username, password, oauthData, listener)
|
||||
}
|
||||
|
||||
private fun refreshToken(context: Context, url: String, oauthData: OAuthData, listener: InstanceListener) {
|
||||
val registerUrl = url + BASE_API_ENDPOINT+ GET_USER_CLIENT_ENDPOINT
|
||||
oauthManager.refreshToken(context, registerUrl, oauthData, listener)
|
||||
}
|
||||
|
||||
fun createLive(context: Context, url: String, oauthData: OAuthData, streamSettings: StreamSettings, listener: InstanceListener) {
|
||||
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)
|
||||
if (oauth != null) {
|
||||
DatabaseManager.updateCredentials(context,oauth)
|
||||
listener.onUpdateOAuthData(oauth)
|
||||
createLiveImpl(context, url,oauth,streamSettings,listener)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(error: String?) {
|
||||
listener.onError(error)
|
||||
}
|
||||
|
||||
override fun onUpdateOAuthData(oauthData: OAuthData) {
|
||||
listener.onUpdateOAuthData(oauthData)
|
||||
}
|
||||
|
||||
})
|
||||
} else {
|
||||
createLiveImpl(context, url, oauthData,streamSettings,listener)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getStreamKey(context: Context, url: String, oauthData: OAuthData, liveId: String, listener: InstanceListener) {
|
||||
val liveInfo = "$url$BASE_API_ENDPOINT$CREATE_LIVE_ENDPOINT/$liveId"
|
||||
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))
|
||||
return
|
||||
}
|
||||
val streamData = extractStreamData(response)
|
||||
if (streamData == null) {
|
||||
listener.onError(context.getString(R.string.json_error))
|
||||
return
|
||||
}
|
||||
val results = Bundle()
|
||||
results.putParcelable(EXTRA_DATA,streamData)
|
||||
listener.onSuccess(results)
|
||||
}
|
||||
|
||||
override fun onError(error: String?) {
|
||||
listener.onError(error)
|
||||
}
|
||||
|
||||
override fun onUpdateOAuthData(oauthData: OAuthData) {
|
||||
listener.onUpdateOAuthData(oauthData)
|
||||
}
|
||||
|
||||
}
|
||||
oauthManager.get(context,liveInfo,oauthData,internalListener)
|
||||
}
|
||||
|
||||
private fun createLiveImpl(context: Context, url: String, oauthData: OAuthData, streamSettings: StreamSettings, listener: InstanceListener) {
|
||||
// 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)
|
||||
if(streamSettings.category != null)
|
||||
formData += prepareFormData(boundary,"category",streamSettings.category.toString(),false)
|
||||
if(streamSettings.language != null)
|
||||
formData += prepareFormData(boundary,"language",streamSettings.language.toString(),false)
|
||||
if(streamSettings.description != null)
|
||||
formData += prepareFormData(boundary,"description",streamSettings.description.toString(),false)
|
||||
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(),false)
|
||||
formData += prepareFormData(boundary,"saveReplay",streamSettings.saveReplay.toString(),true)
|
||||
data.putString(CONTENT_TYPE,"multipart/form-data; boundary=$boundary")
|
||||
data.putString(CONTENT_DATA,formData)
|
||||
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))
|
||||
return
|
||||
}
|
||||
|
||||
val liveId = extractLiveId(response)
|
||||
if (liveId == null) {
|
||||
listener.onError(context.getString(R.string.json_error))
|
||||
return
|
||||
}
|
||||
getStreamKey(context,url,oauthData,liveId,listener)
|
||||
}
|
||||
|
||||
override fun onError(error: String?) {
|
||||
listener.onError(error)
|
||||
}
|
||||
|
||||
override fun onUpdateOAuthData(oauthData: OAuthData) {
|
||||
listener.onUpdateOAuthData(oauthData)
|
||||
}
|
||||
|
||||
}
|
||||
oauthManager.post(context,createLiveUrl,oauthData,data,internalListener)
|
||||
}
|
||||
|
||||
fun getUserChannelList(context: Context, url: String, oauthData: OAuthData, listener: InstanceListener) {
|
||||
if(oauthData.expires < Calendar.getInstance().timeInMillis) {
|
||||
refreshToken(context,url,oauthData,object: InstanceListener {
|
||||
override fun onSuccess(args: Bundle?) {
|
||||
val oauth: OAuthData? = args?.getParcelable(EXTRA_DATA)
|
||||
if (oauth != null) {
|
||||
listener.onUpdateOAuthData(oauth)
|
||||
DatabaseManager.updateCredentials(context,oauth)
|
||||
getUserChannelListImpl(context, url,oauth,listener)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(error: String?) {
|
||||
listener.onError(error)
|
||||
}
|
||||
|
||||
override fun onUpdateOAuthData(oauthData: OAuthData) {
|
||||
listener.onUpdateOAuthData(oauthData)
|
||||
}
|
||||
|
||||
})
|
||||
} else {
|
||||
getUserChannelListImpl(context, url, oauthData, listener)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getUserChannelListImpl(context: Context, url: String, oauthData: OAuthData, listener: InstanceListener) {
|
||||
val userInfoUrl: String = url + BASE_API_ENDPOINT + GET_USER_INFO_ENDPOINT
|
||||
val internalListener : InstanceListener = 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))
|
||||
return
|
||||
}
|
||||
|
||||
val channelList = extractChannelData(response)
|
||||
if (channelList == null) {
|
||||
listener.onError(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 onUpdateOAuthData(oauthData: OAuthData) {
|
||||
listener.onUpdateOAuthData(oauthData)
|
||||
}
|
||||
}
|
||||
|
||||
oauthManager.get(context,userInfoUrl,oauthData,internalListener)
|
||||
}
|
||||
|
||||
fun getCategoryList(context: Context, url: String, listener: InstanceListener) {
|
||||
val userInfoUrl: String = url + BASE_API_ENDPOINT + GET_CATEGORY_ENDPOINT
|
||||
val internalListener : InstanceListener = 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))
|
||||
return
|
||||
}
|
||||
|
||||
val categoryList = extractMapData<Int>(response,0)
|
||||
if (categoryList == null) {
|
||||
listener.onError(context.getString(R.string.json_error))
|
||||
return
|
||||
}
|
||||
categoryList[""]=0
|
||||
args.putSerializable(EXTRA_DATA, categoryList)
|
||||
listener.onSuccess(args)
|
||||
}
|
||||
|
||||
override fun onError(error: String?) {
|
||||
listener.onError(error)
|
||||
}
|
||||
|
||||
override fun onUpdateOAuthData(oauthData: OAuthData) {
|
||||
listener.onUpdateOAuthData(oauthData)
|
||||
}
|
||||
}
|
||||
|
||||
oauthManager.get(context,userInfoUrl,null,internalListener)
|
||||
}
|
||||
|
||||
|
||||
fun getPrivacyList(context: Context, url: String, listener: InstanceListener) {
|
||||
val userInfoUrl: String = url + BASE_API_ENDPOINT + GET_PRIVACY_ENDPOINT
|
||||
val internalListener : InstanceListener = 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))
|
||||
return
|
||||
}
|
||||
|
||||
val privacyList = extractMapData<Int>(response,0)
|
||||
if (privacyList == null) {
|
||||
listener.onError(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 onUpdateOAuthData(oauthData: OAuthData) {
|
||||
listener.onUpdateOAuthData(oauthData)
|
||||
}
|
||||
}
|
||||
|
||||
oauthManager.get(context,userInfoUrl,null,internalListener)
|
||||
}
|
||||
|
||||
fun getLicencesList(context: Context, url: String, listener: InstanceListener) {
|
||||
val userInfoUrl: String = url + BASE_API_ENDPOINT + GET_LICENCE_ENDPOINT
|
||||
val internalListener : InstanceListener = 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))
|
||||
return
|
||||
}
|
||||
|
||||
val licencesList = extractMapData<Int>(response,0)
|
||||
if (licencesList == null) {
|
||||
listener.onError(context.getString(R.string.json_error))
|
||||
return
|
||||
}
|
||||
licencesList[""]=0
|
||||
args.putSerializable(EXTRA_DATA, licencesList)
|
||||
listener.onSuccess(args)
|
||||
}
|
||||
|
||||
override fun onError(error: String?) {
|
||||
listener.onError(error)
|
||||
}
|
||||
|
||||
override fun onUpdateOAuthData(oauthData: OAuthData) {
|
||||
listener.onUpdateOAuthData(oauthData)
|
||||
}
|
||||
}
|
||||
|
||||
oauthManager.get(context,userInfoUrl,null,internalListener)
|
||||
}
|
||||
|
||||
fun getLanguageList(context: Context, url: String, listener: InstanceListener) {
|
||||
val userInfoUrl: String = url + BASE_API_ENDPOINT + GET_LANGUAGES_ENDPOINT
|
||||
val internalListener : InstanceListener = 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))
|
||||
return
|
||||
}
|
||||
|
||||
val languageList = extractMapData<String>(response,"")
|
||||
if (languageList == null) {
|
||||
listener.onError(context.getString(R.string.json_error))
|
||||
return
|
||||
}
|
||||
languageList[""]=""
|
||||
args.putSerializable(EXTRA_DATA, languageList)
|
||||
listener.onSuccess(args)
|
||||
}
|
||||
|
||||
override fun onError(error: String?) {
|
||||
listener.onError(error)
|
||||
}
|
||||
|
||||
override fun onUpdateOAuthData(oauthData: OAuthData) {
|
||||
listener.onUpdateOAuthData(oauthData)
|
||||
}
|
||||
}
|
||||
|
||||
oauthManager.get(context,userInfoUrl,null,internalListener)
|
||||
}
|
||||
|
||||
private fun extractChannelData(response: String): ArrayList<ChannelData>? {
|
||||
try {
|
||||
val json = JSONObject(response)
|
||||
|
||||
if (json.has(VIDEO_CHANNEL)) {
|
||||
val channelList: ArrayList<ChannelData> = ArrayList()
|
||||
val channels = json.getJSONArray(VIDEO_CHANNEL)
|
||||
for (i: Int in 0 until channels.length()) {
|
||||
val channel = channels.getJSONObject(i)
|
||||
if (channel.has(CHANNEL_NAME) && channel.has(CHANNEL_ID)) {
|
||||
val name = channel.getString(CHANNEL_NAME)
|
||||
val id = channel.getLong(CHANNEL_ID)
|
||||
val channelData = ChannelData(id, name)
|
||||
channelList.add(channelData)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
return channelList
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractLiveId(response: String): String? {
|
||||
try {
|
||||
val json = JSONObject(response)
|
||||
|
||||
return if(json.has(VIDEO)) {
|
||||
val video = json.getJSONObject(VIDEO)
|
||||
if (video.has(UUID)) {
|
||||
video.getString(UUID)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun extractStreamData(response: String): StreamData? {
|
||||
try {
|
||||
val json = JSONObject(response)
|
||||
|
||||
return if (json.has(RTMP_URL) && json.has(STREAM_KEY)) {
|
||||
val rtmp = json.getString(RTMP_URL)
|
||||
val key = json.getString(STREAM_KEY)
|
||||
|
||||
StreamData(rtmp,key)
|
||||
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> extractMapData(response: String, type: T): HashMap<String,T>? {
|
||||
return try {
|
||||
val json = JSONObject(response)
|
||||
val map = HashMap<String,T>()
|
||||
for(key in json.keys()) {
|
||||
if (type is Int)
|
||||
map[json.getString(key)] = key.toInt() as T
|
||||
else if (type is String)
|
||||
map[json.getString(key)] = key as T
|
||||
}
|
||||
map
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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 += "$crlf--$boundary--$crlf"
|
||||
}
|
||||
|
||||
return formData
|
||||
}
|
||||
|
||||
|
||||
interface InstanceListener{
|
||||
fun onSuccess(args: Bundle?)
|
||||
fun onError(error: String?)
|
||||
fun onUpdateOAuthData(oauthData: OAuthData)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,532 @@
|
|||
package fr.mobdev.peertubelive.manager
|
||||
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.NetworkCapabilities
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import fr.mobdev.peertubelive.BuildConfig
|
||||
import fr.mobdev.peertubelive.R
|
||||
import fr.mobdev.peertubelive.objects.OAuthData
|
||||
import org.json.JSONObject
|
||||
import java.io.BufferedReader
|
||||
import java.io.InputStream
|
||||
import java.io.InputStreamReader
|
||||
import java.io.OutputStream
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.net.UnknownHostException
|
||||
import java.util.*
|
||||
import java.util.concurrent.Semaphore
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class OAuthManager {
|
||||
|
||||
constructor() {
|
||||
start()
|
||||
}
|
||||
|
||||
fun register(context: Context, url: String, listener: InstanceManager.InstanceListener) {
|
||||
val args = Bundle()
|
||||
args.putString(URL, url)
|
||||
|
||||
val message = Message()
|
||||
message.type = Message.Message_Type.REGISTER
|
||||
message.context = context
|
||||
message.args = args
|
||||
message.listener = listener
|
||||
|
||||
addMessage(message)
|
||||
}
|
||||
|
||||
fun getUserToken(context: Context, url: String, username: String, password: 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)
|
||||
|
||||
val message = Message()
|
||||
message.type = Message.Message_Type.GET_USER_TOKEN
|
||||
message.context = context
|
||||
message.args = args
|
||||
message.listener = listener
|
||||
|
||||
addMessage(message)
|
||||
}
|
||||
|
||||
fun refreshToken(context: Context, url: String, oauthData: OAuthData, listener: InstanceManager.InstanceListener) {
|
||||
val args = Bundle()
|
||||
args.putString(URL, url)
|
||||
args.putParcelable(OAUTH_DATA, oauthData)
|
||||
|
||||
val message = Message()
|
||||
message.type = Message.Message_Type.REFRESH_TOKEN
|
||||
message.context = context
|
||||
message.args = args
|
||||
message.listener = listener
|
||||
|
||||
addMessage(message)
|
||||
}
|
||||
|
||||
fun post(context: Context, url: String, oauthData: OAuthData, data: Bundle, listener: InstanceManager.InstanceListener) {
|
||||
val args = Bundle()
|
||||
args.putString(URL, url)
|
||||
args.putParcelable(OAUTH_DATA, oauthData)
|
||||
args.putBundle(DATA, data)
|
||||
|
||||
val message = Message()
|
||||
message.type = Message.Message_Type.POST
|
||||
message.context = context
|
||||
message.args = args
|
||||
message.listener = listener
|
||||
|
||||
addMessage(message)
|
||||
}
|
||||
|
||||
fun get(context: Context, url: String, oauthData: OAuthData?, listener: InstanceManager.InstanceListener) {
|
||||
val args = Bundle()
|
||||
args.putString(URL, url)
|
||||
args.putParcelable(OAUTH_DATA, oauthData)
|
||||
|
||||
val message = Message()
|
||||
message.type = Message.Message_Type.GET
|
||||
message.context = context
|
||||
message.args = args
|
||||
message.listener = listener
|
||||
|
||||
addMessage(message)
|
||||
}
|
||||
|
||||
private companion object OAuthThread : Thread() {
|
||||
private val messageQueue: ArrayList<Message> = ArrayList()
|
||||
private val sem: Semaphore = Semaphore(0, true)
|
||||
|
||||
private const val URL: String = "URL"
|
||||
private const val USERNAME: String = "USERNAME"
|
||||
private const val PASSWORD: String = "PASSWORD"
|
||||
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)
|
||||
{
|
||||
messageQueue.add(message)
|
||||
sem.release()
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
var isRunning = true
|
||||
while (isRunning) {
|
||||
try {
|
||||
sem.acquire()
|
||||
val mes: Message = messageQueue.removeAt(0)
|
||||
when (mes.type) {
|
||||
Message.Message_Type.REGISTER -> register(mes)
|
||||
Message.Message_Type.GET_USER_TOKEN -> getUserToken(mes)
|
||||
Message.Message_Type.REFRESH_TOKEN -> refreshToken(mes)
|
||||
Message.Message_Type.POST -> post(mes)
|
||||
Message.Message_Type.GET -> get(mes)
|
||||
else -> {}
|
||||
}
|
||||
} catch (e: InterruptedException) {
|
||||
isRunning = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun register(message: Message) {
|
||||
if (!isConnectedToInternet(message.context)) {
|
||||
message.listener?.onError(message.context.getString(R.string.network_error))
|
||||
return
|
||||
}
|
||||
val url: String = message.args.getString(URL,"")
|
||||
val registerUrl = URL(url)
|
||||
val connection: HttpURLConnection = registerUrl.openConnection() as HttpURLConnection
|
||||
connection.requestMethod = "GET"
|
||||
connection.setRequestProperty("User-Agent", "Peeriscope")
|
||||
connection.setRequestProperty("Content-Type", "application/json")
|
||||
connection.setRequestProperty("Accept", "application/json")
|
||||
connection.doInput = true
|
||||
|
||||
var inputStream: InputStream
|
||||
var inError = false
|
||||
try {
|
||||
inputStream = connection.inputStream
|
||||
} catch (e: UnknownHostException) {
|
||||
message.listener?.onError(message.context.getString(R.string.unknown_host))
|
||||
return
|
||||
} catch (e : Exception) {
|
||||
e.printStackTrace()
|
||||
inputStream = connection.errorStream
|
||||
inError = true
|
||||
}
|
||||
|
||||
val response = readInputStream(inputStream)
|
||||
if(!inError) {
|
||||
if(response.isNotEmpty()) {
|
||||
val rootObj = JSONObject(response)
|
||||
|
||||
val clientId = rootObj.getString("client_id")
|
||||
val clientSecret = rootObj.getString("client_secret")
|
||||
|
||||
val oauthData = OAuthData(null, null, clientId, clientSecret, null, null, 0, null,0)
|
||||
val result = Bundle()
|
||||
result.putParcelable(EXTRA_DATA, oauthData)
|
||||
|
||||
message.listener?.onSuccess(result)
|
||||
} else {
|
||||
message.listener?.onError(message.context.getString(R.string.unknwon_error))
|
||||
}
|
||||
} else {
|
||||
if(response.isNotEmpty()) {
|
||||
val rootObj = JSONObject(response)
|
||||
val error = rootObj.getString("error")
|
||||
message.listener?.onError(error)
|
||||
} else {
|
||||
message.listener?.onError(message.context.getString(R.string.unknwon_error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getUserToken(message: Message) {
|
||||
if (!isConnectedToInternet(message.context)) {
|
||||
message.listener?.onError(message.context.getString(R.string.network_error))
|
||||
return
|
||||
}
|
||||
val url: String = message.args.getString(URL, "")
|
||||
val getUserTokenUrl = URL(url)
|
||||
val connection: HttpURLConnection = getUserTokenUrl.openConnection() as HttpURLConnection
|
||||
connection.requestMethod = "POST"
|
||||
connection.setRequestProperty("User-Agent", "Peeriscope")
|
||||
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded")
|
||||
connection.setRequestProperty("Accept", "application/json")
|
||||
connection.doInput = true
|
||||
connection.doOutput = true
|
||||
|
||||
val oauth: OAuthData? = message.args.getParcelable(OAUTH_DATA)
|
||||
if (BuildConfig.DEBUG && oauth == null) {
|
||||
error("Missing OAUTH DATA")
|
||||
}
|
||||
val username: String = message.args.getString(USERNAME,"")
|
||||
val password: String = message.args.getString(PASSWORD,"")
|
||||
|
||||
var output = ""
|
||||
output += "client_id=" + (oauth?.clientId ?: "")
|
||||
output += "&client_secret=" + (oauth?.clientSecret ?: "")
|
||||
output += "&grant_type=password"
|
||||
output += "&response_type=code"
|
||||
output += "&username=$username"
|
||||
output += "&password=$password"
|
||||
|
||||
val outputStream = connection.outputStream
|
||||
outputStream.write(output.toByteArray())
|
||||
|
||||
var inputStream: InputStream
|
||||
var inError = false
|
||||
try {
|
||||
inputStream = connection.inputStream
|
||||
} catch (e: UnknownHostException) {
|
||||
message.listener?.onError(message.context.getString(R.string.unknown_host))
|
||||
return
|
||||
} catch (e : Exception) {
|
||||
e.printStackTrace()
|
||||
inputStream = connection.errorStream
|
||||
inError = true
|
||||
}
|
||||
|
||||
val response = readInputStream(inputStream)
|
||||
|
||||
if(!inError) {
|
||||
if(response.isNotEmpty()) {
|
||||
val rootObj = JSONObject(response)
|
||||
|
||||
val accessToken = rootObj.getString("access_token")
|
||||
val tokenType = rootObj.getString("token_type")
|
||||
var expires = Calendar.getInstance().timeInMillis
|
||||
expires += rootObj.getLong("expires_in")*1000
|
||||
|
||||
val refreshToken = rootObj.getString("refresh_token")
|
||||
var refreshTokenExpires = Calendar.getInstance().timeInMillis
|
||||
refreshTokenExpires += rootObj.getLong("refresh_token_expires_in")*1000
|
||||
|
||||
val oauthData = OAuthData(oauth?.baseUrl,username,oauth?.clientId, oauth?.clientSecret, accessToken, tokenType, expires, refreshToken, refreshTokenExpires)
|
||||
val result = Bundle()
|
||||
result.putParcelable(EXTRA_DATA, oauthData)
|
||||
|
||||
message.listener?.onSuccess(result)
|
||||
} else {
|
||||
message.listener?.onError(message.context.getString(R.string.unknwon_error))
|
||||
}
|
||||
} else {
|
||||
if(response.isNotEmpty()) {
|
||||
val rootObj = JSONObject(response)
|
||||
val error = rootObj.getString("error")
|
||||
message.listener?.onError(error)
|
||||
} else {
|
||||
message.listener?.onError(message.context.getString(R.string.unknwon_error))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun refreshToken(message: Message) {
|
||||
if (!isConnectedToInternet(message.context)) {
|
||||
message.listener?.onError(message.context.getString(R.string.network_error))
|
||||
return
|
||||
}
|
||||
val url: String = message.args.getString(URL, "")
|
||||
val getUserTokenUrl = URL(url)
|
||||
val connection: HttpURLConnection = getUserTokenUrl.openConnection() as HttpURLConnection
|
||||
connection.requestMethod = "POST"
|
||||
connection.setRequestProperty("User-Agent", "Peeriscope")
|
||||
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded")
|
||||
connection.setRequestProperty("Accept", "application/json")
|
||||
connection.doInput = true
|
||||
connection.doOutput = true
|
||||
|
||||
val oauth: OAuthData? = message.args.getParcelable(OAUTH_DATA)
|
||||
if (BuildConfig.DEBUG && oauth == null) {
|
||||
error("Missing OAUTH DATA")
|
||||
}
|
||||
|
||||
var output = ""
|
||||
output += "client_id=" + (oauth?.clientId ?: "")
|
||||
output += "&client_secret=" + (oauth?.clientSecret ?: "")
|
||||
output += "&grant_type=refresh_token"
|
||||
output += "&response_type=code"
|
||||
output += "&refresh_token=" +(oauth?.refreshToken?: "")
|
||||
|
||||
val outputStream = connection.outputStream
|
||||
outputStream.write(output.toByteArray())
|
||||
|
||||
var inputStream: InputStream
|
||||
var inError = false
|
||||
try {
|
||||
inputStream = connection.inputStream
|
||||
} catch (e: UnknownHostException) {
|
||||
message.listener?.onError(message.context.getString(R.string.unknown_host))
|
||||
return
|
||||
} catch (e : Exception) {
|
||||
e.printStackTrace()
|
||||
inputStream = connection.errorStream
|
||||
inError = true
|
||||
}
|
||||
|
||||
val response = readInputStream(inputStream)
|
||||
|
||||
if(!inError) {
|
||||
if(response.isNotEmpty()) {
|
||||
val rootObj = JSONObject(response)
|
||||
|
||||
val accessToken = rootObj.getString("access_token")
|
||||
val tokenType = rootObj.getString("token_type")
|
||||
var expires = Calendar.getInstance().timeInMillis
|
||||
expires += rootObj.getLong("expires_in")*1000
|
||||
val refreshToken = rootObj.getString("refresh_token")
|
||||
var refreshTokenExpires = Calendar.getInstance().timeInMillis
|
||||
refreshTokenExpires += rootObj.getLong("refresh_token_expires_in")*1000
|
||||
|
||||
val oauthData = OAuthData(oauth?.baseUrl,oauth?.username,oauth?.clientId, oauth?.clientSecret, accessToken, tokenType, expires, refreshToken, refreshTokenExpires)
|
||||
val result = Bundle()
|
||||
result.putParcelable(EXTRA_DATA, oauthData)
|
||||
|
||||
message.listener?.onSuccess(result)
|
||||
} else {
|
||||
message.listener?.onError(message.context.getString(R.string.unknwon_error))
|
||||
}
|
||||
} else {
|
||||
if(response.isNotEmpty()) {
|
||||
val rootObj = JSONObject(response)
|
||||
val error = rootObj.getString("error")
|
||||
message.listener?.onError(error)
|
||||
} else {
|
||||
message.listener?.onError(message.context.getString(R.string.unknwon_error))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun post(message: Message) {
|
||||
if (!isConnectedToInternet(message.context)) {
|
||||
message.listener?.onError(message.context.getString(R.string.network_error))
|
||||
return
|
||||
}
|
||||
val url: String = message.args.getString(URL, "")
|
||||
val postUrl = URL(url)
|
||||
val data = message.args.getBundle(DATA)!!
|
||||
val connection: HttpURLConnection = postUrl.openConnection() as HttpURLConnection
|
||||
connection.requestMethod = "POST"
|
||||
connection.setRequestProperty("User-Agent", "Peeriscope")
|
||||
connection.setRequestProperty("Content-Type", data.getString(CONTENT_TYPE,"application/json"))
|
||||
connection.setRequestProperty("Accept", "application/json")
|
||||
connection.doInput = true
|
||||
connection.doOutput = true
|
||||
|
||||
val oauth: OAuthData? = message.args.getParcelable(OAUTH_DATA)
|
||||
if (BuildConfig.DEBUG && oauth == null) {
|
||||
error("Missing OAUTH DATA")
|
||||
}
|
||||
val extraData: String = data.getString(CONTENT_DATA,"")
|
||||
connection.setRequestProperty("Authorization","Bearer ${oauth?.accessToken}")
|
||||
|
||||
var inputStream: InputStream? = null
|
||||
val outputStream: OutputStream
|
||||
var inError = false
|
||||
try {
|
||||
outputStream = connection.outputStream
|
||||
outputStream.write(extraData.toByteArray())
|
||||
outputStream.flush()
|
||||
outputStream.close()
|
||||
} catch (e: UnknownHostException) {
|
||||
message.listener?.onError(message.context.getString(R.string.unknown_host))
|
||||
return
|
||||
} catch (e : Exception) {
|
||||
e.printStackTrace()
|
||||
inError = true
|
||||
inputStream = connection.errorStream
|
||||
}
|
||||
|
||||
|
||||
if(!inError) {
|
||||
try {
|
||||
inputStream = connection.inputStream
|
||||
} catch (e: UnknownHostException) {
|
||||
message.listener?.onError(message.context.getString(R.string.unknown_host))
|
||||
return
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
inputStream = connection.errorStream
|
||||
inError = true
|
||||
}
|
||||
}
|
||||
if (inputStream != null) {
|
||||
val response = readInputStream(inputStream)
|
||||
if (!inError) {
|
||||
if (response.isNotEmpty()) {
|
||||
val result = Bundle()
|
||||
result.putString(EXTRA_DATA, response)
|
||||
message.listener?.onSuccess(result)
|
||||
} else {
|
||||
message.listener?.onError(message.context.getString(R.string.unknwon_error))
|
||||
}
|
||||
} else {
|
||||
if (response.isNotEmpty()) {
|
||||
try {
|
||||
val rootObj = JSONObject(response)
|
||||
val error = rootObj.getString("error")
|
||||
message.listener?.onError(error)
|
||||
} catch (e: Exception) {
|
||||
message.listener?.onError(message.context.getString(R.string.json_error))
|
||||
}
|
||||
} else {
|
||||
message.listener?.onError(message.context.getString(R.string.unknwon_error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun get(message: Message) {
|
||||
if (!isConnectedToInternet(message.context)) {
|
||||
message.listener?.onError(message.context.getString(R.string.network_error))
|
||||
return
|
||||
}
|
||||
val url: String = message.args.getString(URL, "")
|
||||
val getUserTokenUrl = URL(url)
|
||||
val connection: HttpURLConnection = getUserTokenUrl.openConnection() as HttpURLConnection
|
||||
connection.requestMethod = "GET"
|
||||
connection.setRequestProperty("User-Agent", "Peeriscope")
|
||||
connection.setRequestProperty("Content-Type", "application/json")
|
||||
connection.setRequestProperty("Accept", "application/json")
|
||||
|
||||
connection.doInput = true
|
||||
|
||||
val oauth: OAuthData? = message.args.getParcelable(OAUTH_DATA)
|
||||
if (oauth != null) {
|
||||
connection.setRequestProperty("Authorization", "Bearer ${oauth.accessToken}")
|
||||
}
|
||||
|
||||
var inputStream: InputStream
|
||||
var inError = false
|
||||
try {
|
||||
inputStream = connection.inputStream
|
||||
} catch (e: UnknownHostException) {
|
||||
message.listener?.onError(message.context.getString(R.string.unknown_host))
|
||||
return
|
||||
} catch (e : Exception) {
|
||||
e.printStackTrace()
|
||||
inputStream = connection.errorStream
|
||||
inError = true
|
||||
}
|
||||
|
||||
val response = readInputStream(inputStream)
|
||||
if(!inError) {
|
||||
if(response.isNotEmpty()) {
|
||||
val result = Bundle()
|
||||
result.putString(EXTRA_DATA, response)
|
||||
message.listener?.onSuccess(result)
|
||||
} else {
|
||||
message.listener?.onError(message.context.getString(R.string.unknwon_error))
|
||||
}
|
||||
} else {
|
||||
if(response.isNotEmpty()) {
|
||||
try {
|
||||
val rootObj = JSONObject(response)
|
||||
val error = rootObj.getString("error")
|
||||
message.listener?.onError(error)
|
||||
} catch (e: Exception) {
|
||||
message.listener?.onError(message.context.getString(R.string.json_error))
|
||||
}
|
||||
} else {
|
||||
message.listener?.onError(message.context.getString(R.string.unknwon_error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun readInputStream(inputStream: InputStream) : String {
|
||||
val inReader = InputStreamReader(inputStream)
|
||||
val bufReader = BufferedReader(inReader)
|
||||
var line: String?
|
||||
var response = ""
|
||||
|
||||
do {
|
||||
line = bufReader.readLine()
|
||||
if(line != null)
|
||||
response += line
|
||||
|
||||
}while (line != null)
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
private fun isConnectedToInternet(context: Context): Boolean {
|
||||
//verify the connectivity
|
||||
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
val network = connectivityManager.activeNetwork
|
||||
val capabilities = connectivityManager.getNetworkCapabilities(network)
|
||||
if(capabilities != null)
|
||||
return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) || capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
|
||||
} else {
|
||||
val networkInfo = connectivityManager.activeNetworkInfo
|
||||
if (networkInfo != null)
|
||||
return networkInfo.isConnected
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private class Message {
|
||||
var type: Message_Type = Message_Type.UNKNOWN
|
||||
var args = Bundle()
|
||||
var listener: InstanceManager.InstanceListener? = null
|
||||
lateinit var context: Context
|
||||
|
||||
enum class Message_Type {
|
||||
REGISTER, GET_USER_TOKEN, REFRESH_TOKEN, POST, GET, UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package fr.mobdev.peertubelive.objects
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
|
||||
class ChannelData(val id: Long, val name: String?): Parcelable {
|
||||
constructor(parcel: Parcel) : this(
|
||||
parcel.readLong(),
|
||||
parcel.readString()
|
||||
) {
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||
dest.writeLong(id)
|
||||
dest.writeString(name)
|
||||
}
|
||||
|
||||
companion object CREATOR : Parcelable.Creator<ChannelData> {
|
||||
override fun createFromParcel(parcel: Parcel): ChannelData {
|
||||
return ChannelData(parcel)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<ChannelData?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package fr.mobdev.peertubelive.objects
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
|
||||
class OAuthData(var baseUrl: String?, var username: String?, var clientId: String?, var clientSecret: String?, var accessToken: String?, var tokenType: String?, var expires: Long, var refreshToken: String?, var refreshTokenExpires: Long) : Parcelable {
|
||||
constructor(parcel: Parcel) : this(
|
||||
parcel.readString(),
|
||||
parcel.readString(),
|
||||
parcel.readString(),
|
||||
parcel.readString(),
|
||||
parcel.readString(),
|
||||
parcel.readString(),
|
||||
parcel.readLong(),
|
||||
parcel.readString(),
|
||||
parcel.readLong()
|
||||
) {
|
||||
}
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
parcel.writeString(baseUrl)
|
||||
parcel.writeString(username)
|
||||
parcel.writeString(clientId)
|
||||
parcel.writeString(clientSecret)
|
||||
parcel.writeString(accessToken)
|
||||
parcel.writeString(tokenType)
|
||||
parcel.writeLong(expires)
|
||||
parcel.writeString(refreshToken)
|
||||
parcel.writeLong(refreshTokenExpires)
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
fun updateData(oAuthData: OAuthData){
|
||||
baseUrl = oAuthData.baseUrl
|
||||
username = oAuthData.username
|
||||
clientId = oAuthData.clientId
|
||||
clientSecret = oAuthData.clientSecret
|
||||
accessToken = oAuthData.accessToken
|
||||
tokenType = oAuthData.tokenType
|
||||
expires = oAuthData.expires
|
||||
refreshToken = oAuthData.refreshToken
|
||||
refreshTokenExpires = oAuthData.refreshTokenExpires
|
||||
|
||||
}
|
||||
|
||||
companion object CREATOR : Parcelable.Creator<OAuthData> {
|
||||
override fun createFromParcel(parcel: Parcel): OAuthData {
|
||||
return OAuthData(parcel)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<OAuthData?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package fr.mobdev.peertubelive.objects
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
|
||||
class StreamData(val url: String?, val key: String?): Parcelable {
|
||||
constructor(parcel: Parcel) : this(
|
||||
parcel.readString(),
|
||||
parcel.readString()
|
||||
) {
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||
dest.writeString(url)
|
||||
dest.writeString(key)
|
||||
}
|
||||
|
||||
companion object CREATOR : Parcelable.Creator<StreamData> {
|
||||
override fun createFromParcel(parcel: Parcel): StreamData {
|
||||
return StreamData(parcel)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<StreamData?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package fr.mobdev.peertubelive.objects
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
|
||||
class StreamSettings(
|
||||
val title: String, val channel: Long, val privacy: Int, val category: Int?, val language: String?, val licence: Int?, val description: String?,
|
||||
val comments: Boolean, val download: Boolean, val nsfw: Boolean, val saveReplay: Boolean) : Parcelable {
|
||||
constructor(parcel: Parcel) : this(
|
||||
parcel.readString()!!,
|
||||
parcel.readLong(),
|
||||
parcel.readInt(),
|
||||
parcel.readInt(),
|
||||
parcel.readString(),
|
||||
parcel.readInt(),
|
||||
parcel.readString(),
|
||||
parcel.readByte() != 0.toByte(),
|
||||
parcel.readByte() != 0.toByte(),
|
||||
parcel.readByte() != 0.toByte(),
|
||||
parcel.readByte() != 0.toByte()
|
||||
) {
|
||||
}
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
parcel.writeString(title)
|
||||
parcel.writeLong(channel)
|
||||
parcel.writeInt(privacy)
|
||||
if (category != null) {
|
||||
parcel.writeInt(category)
|
||||
}
|
||||
parcel.writeString(language)
|
||||
if (licence != null) {
|
||||
parcel.writeInt(licence)
|
||||
}
|
||||
parcel.writeString(description)
|
||||
parcel.writeByte(if (comments) 1 else 0)
|
||||
parcel.writeByte(if (download) 1 else 0)
|
||||
parcel.writeByte(if (nsfw) 1 else 0)
|
||||
parcel.writeByte(if (saveReplay) 1 else 0)
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
companion object CREATOR : Parcelable.Creator<StreamSettings> {
|
||||
override fun createFromParcel(parcel: Parcel): StreamSettings {
|
||||
return StreamSettings(parcel)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<StreamSettings?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,271 @@
|
|||
package fr.mobdev.peertubelive.utils
|
||||
|
||||
import fr.mobdev.peertubelive.R
|
||||
|
||||
class TranslationUtils {
|
||||
companion object {
|
||||
private val categoryMap: Map<String, Int> = mapOf(
|
||||
"Music" to R.string.music,
|
||||
"Films" to R.string.films,
|
||||
"Vehicles" to R.string.vehicles,
|
||||
"Art" to R.string.art,
|
||||
"Sports" to R.string.sports,
|
||||
"Travels" to R.string.travels,
|
||||
"Gaming" to R.string.gaming,
|
||||
"People" to R.string.people,
|
||||
"Comedy" to R.string.comedy,
|
||||
"Entertainment" to R.string.entertainment,
|
||||
"News & Politics" to R.string.news_politics,
|
||||
"How To" to R.string.how_to,
|
||||
"Education" to R.string.education,
|
||||
"Activism" to R.string.activism,
|
||||
"Science & Technology" to R.string.science_tech,
|
||||
"Animals" to R.string.animals,
|
||||
"Kids" to R.string.kids,
|
||||
"Food" to R.string.food
|
||||
)
|
||||
|
||||
fun getCategoryTranslationFor(category: String):Int {
|
||||
return if (categoryMap.containsKey(category))
|
||||
categoryMap[category]!!
|
||||
else
|
||||
-1
|
||||
}
|
||||
|
||||
private val licenceMap: Map<String, Int> = mapOf(
|
||||
"Attribution" to R.string.by,
|
||||
"Attribution - Share Alike" to R.string.bysa,
|
||||
"Attribution - No Derivatives" to R.string.bynd,
|
||||
"Attribution - Non Commercial" to R.string.bync,
|
||||
"Attribution - Non Commercial - Share Alike" to R.string.byncsa,
|
||||
"Attribution - Non Commercial - No Derivatives" to R.string.byncnd,
|
||||
"Public Domain Dedication" to R.string.public_domain
|
||||
)
|
||||
|
||||
fun getLicenceTranslationFor(licence: String):Int {
|
||||
return if (licenceMap.containsKey(licence))
|
||||
licenceMap[licence]!!
|
||||
else
|
||||
-1
|
||||
}
|
||||
|
||||
private val privacyMap: Map<String, Int> = mapOf(
|
||||
"Public" to R.string.privacy_public,
|
||||
"Private" to R.string.privacy_private,
|
||||
"Internal" to R.string.internal,
|
||||
"Unlisted" to R.string.unlisted
|
||||
)
|
||||
|
||||
fun getPrivacyTranslationFor(privacy: String):Int {
|
||||
return if (privacyMap.containsKey(privacy))
|
||||
privacyMap[privacy]!!
|
||||
else
|
||||
-1
|
||||
}
|
||||
|
||||
private val languageMap: Map<String, Int> = mapOf(
|
||||
"Afar" to R.string.afar,
|
||||
"Abkhazian" to R.string.abkhazian,
|
||||
"Afrikaans" to R.string.afrikaans,
|
||||
"Akan" to R.string.akan,
|
||||
"Amharic" to R.string.amharic,
|
||||
"Arabic" to R.string.arabic,
|
||||
"Aragonese" to R.string.aragonese,
|
||||
"American Sign Language" to R.string.american_sign_language,
|
||||
"Assamese" to R.string.assamese,
|
||||
"Avaric" to R.string.avaric,
|
||||
"Kotava" to R.string.kotava,
|
||||
"Aymara" to R.string.aymara,
|
||||
"Azerbaijani" to R.string.azerbaijani,
|
||||
"Bashkir" to R.string.bashkir,
|
||||
"Bambara" to R.string.bambara,
|
||||
"Belarusian" to R.string.belarusian,
|
||||
"Bengali" to R.string.bengali,
|
||||
"British Sign Language" to R.string.british_sign_language,
|
||||
"Bislama" to R.string.bislama,
|
||||
"Tibetan" to R.string.tibetan,
|
||||
"Bosnian" to R.string.bosnian,
|
||||
"Breton" to R.string.breton,
|
||||
"Bulgarian" to R.string.bulgarian,
|
||||
"Brazilian Sign Language" to R.string.brazilian_sign_language,
|
||||
"Catalan" to R.string.catalan,
|
||||
"Czech" to R.string.czech,
|
||||
"Chamorro" to R.string.chamorro,
|
||||
"Chechen" to R.string.chechen,
|
||||
"Chuvash" to R.string.chuvash,
|
||||
"Cornish" to R.string.cornish,
|
||||
"Corsican" to R.string.corsican,
|
||||
"Cree" to R.string.cree,
|
||||
"Czech Sign Language" to R.string.czech_sign_language,
|
||||
"Chinese Sign Language" to R.string.chinese_sign_language,
|
||||
"Welsh" to R.string.welsh,
|
||||
"Danish" to R.string.danish,
|
||||
"German" to R.string.german,
|
||||
"Dhivehi" to R.string.dhivehi,
|
||||
"Danish Sign Language" to R.string.danish_sign_language,
|
||||
"Dzongkha" to R.string.dzongkha,
|
||||
"Greek" to R.string.greek,
|
||||
"English" to R.string.english,
|
||||
"Esperanto" to R.string.esperanto,
|
||||
"Estonian" to R.string.estonian,
|
||||
"Basque" to R.string.basque,
|
||||
"Ewe" to R.string.ewe,
|
||||
"Faroese" to R.string.faroese,
|
||||
"Persian" to R.string.persian,
|
||||
"Fijian" to R.string.fijian,
|
||||
"Finnish" to R.string.finnish,
|
||||
"French" to R.string.french,
|
||||
"Western Frisian" to R.string.western_frisian,
|
||||
"French Sign Language" to R.string.french_sign_language,
|
||||
"Fulah" to R.string.fulah,
|
||||
"Scottish Gaelic" to R.string.scottish_gaelic,
|
||||
"Irish" to R.string.irish,
|
||||
"Galician" to R.string.galician,
|
||||
"Manx" to R.string.manx,
|
||||
"Guarani" to R.string.guarani,
|
||||
"German Sign Language" to R.string.german_sign_language,
|
||||
"Gujarati" to R.string.gujarati,
|
||||
"Haitian" to R.string.haitian,
|
||||
"Hausa" to R.string.hausa,
|
||||
"Serbo-Croatian" to R.string.serbo_croatian,
|
||||
"Hebrew" to R.string.hebrew,
|
||||
"Herero" to R.string.herero,
|
||||
"Hindi" to R.string.hindi,
|
||||
"Hiri Motu" to R.string.hiri_motu,
|
||||
"Croatian" to R.string.croatian,
|
||||
"Hungarian" to R.string.hungarian,
|
||||
"Armenian" to R.string.armenian,
|
||||
"Igbo" to R.string.igbo,
|
||||
"Sichuan Yi" to R.string.sichuan_yi,
|
||||
"Inuktitut" to R.string.inuktitut,
|
||||
"Indonesian" to R.string.indonesian,
|
||||
"Inupiaq" to R.string.inupiaq,
|
||||
"Icelandic" to R.string.icelandic,
|
||||
"Italian" to R.string.italian,
|
||||
"Javanese" to R.string.javanese,
|
||||
"Lojban" to R.string.lojban,
|
||||
"Japanese" to R.string.japanese,
|
||||
"Japanese Sign Language" to R.string.japanese_sign_language,
|
||||
"Kabyle" to R.string.kabyle,
|
||||
"Kalaallisut" to R.string.kalaallisut,
|
||||
"Kannada" to R.string.kannada,
|
||||
"Kashmiri" to R.string.kashmiri,
|
||||
"Georgian" to R.string.georgian,
|
||||
"Kanuri" to R.string.kanuri,
|
||||
"Kazakh" to R.string.kazakh,
|
||||
"Khmer" to R.string.khmer,
|
||||
"Kikuyu" to R.string.kikuyu,
|
||||
"Kinyarwanda" to R.string.kinyarwanda,
|
||||
"Kirghiz" to R.string.kirghiz,
|
||||
"Komi" to R.string.komi,
|
||||
"Kongo" to R.string.kongo,
|
||||
"Korean" to R.string.korean,
|
||||
"Kuanyama" to R.string.kuanyama,
|
||||
"Kurdish" to R.string.kurdish,
|
||||
"Lao" to R.string.lao,
|
||||
"Latvian" to R.string.latvian,
|
||||
"Limburgan" to R.string.limburgan,
|
||||
"Lingala" to R.string.lingala,
|
||||
"Lithuanian" to R.string.lithuanian,
|
||||
"Luxembourgish" to R.string.luxembourgish,
|
||||
"Luba-Katanga" to R.string.luba_katanga,
|
||||
"Ganda" to R.string.ganda,
|
||||
"Marshallese" to R.string.marshallese,
|
||||
"Malayalam" to R.string.malayalam,
|
||||
"Marathi" to R.string.marathi,
|
||||
"Macedonian" to R.string.macedonian,
|
||||
"Malagasy" to R.string.malagasy,
|
||||
"Maltese" to R.string.maltese,
|
||||
"Mongolian" to R.string.mongolian,
|
||||
"Maori" to R.string.maori,
|
||||
"Malay (macrolanguage)" to R.string.malay_macrolanguage,
|
||||
"Burmese" to R.string.burmese,
|
||||
"Nauru" to R.string.nauru,
|
||||
"Navajo" to R.string.navajo,
|
||||
"South Ndebele" to R.string.south_ndebele,
|
||||
"North Ndebele" to R.string.north_ndebele,
|
||||
"Ndonga" to R.string.ndonga,
|
||||
"Nepali (macrolanguage)" to R.string.nepali_macrolanguage,
|
||||
"Dutch" to R.string.dutch,
|
||||
"Norwegian Nynorsk" to R.string.norwegian_nynorsk,
|
||||
"Norwegian Bokmål" to R.string.norwegian_bokmål,
|
||||
"Norwegian" to R.string.norwegian,
|
||||
"Nyanja" to R.string.nyanja,
|
||||
"Occitan" to R.string.occitan,
|
||||
"Ojibwa" to R.string.ojibwa,
|
||||
"Oriya (macrolanguage)" to R.string.oriya_macrolanguage,
|
||||
"Oromo" to R.string.oromo,
|
||||
"Ossetian" to R.string.ossetian,
|
||||
"Panjabi" to R.string.panjabi,
|
||||
"Pakistan Sign Language" to R.string.pakistan_sign_language,
|
||||
"Polish" to R.string.polish,
|
||||
"Portuguese" to R.string.portuguese,
|
||||
"Pushto" to R.string.pushto,
|
||||
"Quechua" to R.string.quechua,
|
||||
"Romansh" to R.string.romansh,
|
||||
"Romanian" to R.string.romanian,
|
||||
"Russian Sign Language" to R.string.russian_sign_language,
|
||||
"Rundi" to R.string.rundi,
|
||||
"Russian" to R.string.russian,
|
||||
"Sango" to R.string.sango,
|
||||
"Saudi Arabian Sign Language" to R.string.saudi_arabian_sign_language,
|
||||
"South African Sign Language" to R.string.south_african_sign_language,
|
||||
"Sinhala" to R.string.sinhala,
|
||||
"Slovak" to R.string.slovak,
|
||||
"Slovenian" to R.string.slovenian,
|
||||
"Northern Sami" to R.string.northern_sami,
|
||||
"Samoan" to R.string.samoan,
|
||||
"Shona" to R.string.shona,
|
||||
"Sindhi" to R.string.sindhi,
|
||||
"Somali" to R.string.somali,
|
||||
"Southern Sotho" to R.string.southern_sotho,
|
||||
"Spanish" to R.string.spanish,
|
||||
"Albanian" to R.string.albanian,
|
||||
"Sardinian" to R.string.sardinian,
|
||||
"Serbian" to R.string.serbian,
|
||||
"Swati" to R.string.swati,
|
||||
"Sundanese" to R.string.sundanese,
|
||||
"Swahili (macrolanguage)" to R.string.swahili_macrolanguage,
|
||||
"Swedish" to R.string.swedish,
|
||||
"Swedish Sign Language" to R.string.swedish_sign_language,
|
||||
"Tahitian" to R.string.tahitian,
|
||||
"Tamil" to R.string.tamil,
|
||||
"Tatar" to R.string.tatar,
|
||||
"Telugu" to R.string.telugu,
|
||||
"Tajik" to R.string.tajik,
|
||||
"Tagalog" to R.string.tagalog,
|
||||
"Thai" to R.string.thai,
|
||||
"Tigrinya" to R.string.tigrinya,
|
||||
"Klingon" to R.string.klingon,
|
||||
"Tonga (Tonga Islands)" to R.string.tonga_tonga_islands,
|
||||
"Tswana" to R.string.tswana,
|
||||
"Tsonga" to R.string.tsonga,
|
||||
"Turkmen" to R.string.turkmen,
|
||||
"Turkish" to R.string.turkish,
|
||||
"Twi" to R.string.twi,
|
||||
"Uighur" to R.string.uighur,
|
||||
"Ukrainian" to R.string.ukrainian,
|
||||
"Urdu" to R.string.urdu,
|
||||
"Uzbek" to R.string.uzbek,
|
||||
"Venda" to R.string.venda,
|
||||
"Vietnamese" to R.string.vietnamese,
|
||||
"Walloon" to R.string.walloon,
|
||||
"Wolof" to R.string.wolof,
|
||||
"Xhosa" to R.string.xhosa,
|
||||
"Yiddish" to R.string.yiddish,
|
||||
"Yoruba" to R.string.yoruba,
|
||||
"Zhuang" to R.string.zhuang,
|
||||
"Chinese" to R.string.chinese,
|
||||
"Zulu" to R.string.zulu
|
||||
)
|
||||
|
||||
fun getLanguageTranslationFor(language: String):Int {
|
||||
return if (languageMap.containsKey(language))
|
||||
languageMap[language]!!
|
||||
else
|
||||
-1
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="85.84757"
|
||||
android:endY="92.4963"
|
||||
android:startX="42.9492"
|
||||
android:startY="49.59793"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
|
@ -0,0 +1,16 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M16,7h-1l-1,-1h-4L9,7H8C6.9,7 6,7.9 6,9v6c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V9C18,7.9 17.1,7 16,7zM12,14c-1.1,0 -2,-0.9 -2,-2c0,-1.1 0.9,-2 2,-2s2,0.9 2,2C14,13.1 13.1,14 12,14z"/>
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M8.57,0.51l4.48,4.48V2.04c4.72,0.47 8.48,4.23 8.95,8.95c0,0 2,0 2,0C23.34,3.02 15.49,-1.59 8.57,0.51z"/>
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M10.95,21.96C6.23,21.49 2.47,17.73 2,13.01c0,0 -2,0 -2,0c0.66,7.97 8.51,12.58 15.43,10.48l-4.48,-4.48V21.96z"/>
|
||||
</vector>
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M3.27,3L2,4.27l5,5V13h3v9l3.58,-6.14L17.73,20 19,18.73 3.27,3zM17,10h-4l4,-8H7v2.18l8.46,8.46L17,10z"/>
|
||||
</vector>
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M7,2v11h3v9l7,-12h-4l4,-8z"/>
|
||||
</vector>
|
|
@ -0,0 +1,11 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:autoMirrored="true">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z"/>
|
||||
</vector>
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M23.25,12.77l-2.57,-2.57 -1.41,1.41 2.22,2.22 -5.66,5.66L4.51,8.17l5.66,-5.66 2.1,2.1 1.41,-1.41L11.23,0.75c-0.59,-0.59 -1.54,-0.59 -2.12,0L2.75,7.11c-0.59,0.59 -0.59,1.54 0,2.12l12.02,12.02c0.59,0.59 1.54,0.59 2.12,0l6.36,-6.36c0.59,-0.59 0.59,-1.54 0,-2.12zM8.47,20.48C5.2,18.94 2.86,15.76 2.5,12L1,12c0.51,6.16 5.66,11 11.95,11l0.66,-0.03 -3.81,-3.82 -1.33,1.33zM16,9h5c0.55,0 1,-0.45 1,-1L22,4c0,-0.55 -0.45,-1 -1,-1v-0.5C21,1.12 19.88,0 18.5,0S16,1.12 16,2.5L16,3c-0.55,0 -1,0.45 -1,1v4c0,0.55 0.45,1 1,1zM16.8,2.5c0,-0.94 0.76,-1.7 1.7,-1.7s1.7,0.76 1.7,1.7L20.2,3h-3.4v-0.5z"/>
|
||||
</vector>
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M16.48,2.52c3.27,1.55 5.61,4.72 5.97,8.48h1.5C23.44,4.84 18.29,0 12,0l-0.66,0.03 3.81,3.81 1.33,-1.32zM10.23,1.75c-0.59,-0.59 -1.54,-0.59 -2.12,0L1.75,8.11c-0.59,0.59 -0.59,1.54 0,2.12l12.02,12.02c0.59,0.59 1.54,0.59 2.12,0l6.36,-6.36c0.59,-0.59 0.59,-1.54 0,-2.12L10.23,1.75zM14.83,21.19L2.81,9.17l6.36,-6.36 12.02,12.02 -6.36,6.36zM7.52,21.48C4.25,19.94 1.91,16.76 1.55,13L0.05,13C0.56,19.16 5.71,24 12,24l0.66,-0.03 -3.81,-3.81 -1.33,1.32z"/>
|
||||
</vector>
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M17,10.5V7c0,-0.55 -0.45,-1 -1,-1H4c-0.55,0 -1,0.45 -1,1v10c0,0.55 0.45,1 1,1h12c0.55,0 1,-0.45 1,-1v-3.5l4,4v-11l-4,4z"/>
|
||||
</vector>
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M21,6.5l-4,4V7c0,-0.55 -0.45,-1 -1,-1H9.82L21,17.18V6.5zM3.27,2L2,3.27 4.73,6H4c-0.55,0 -1,0.45 -1,1v10c0,0.55 0.45,1 1,1h12c0.21,0 0.39,-0.08 0.54,-0.18L19.73,21 21,19.73 3.27,2z"/>
|
||||
</vector>
|
|
@ -0,0 +1,11 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:autoMirrored="true">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v2.21l2.45,2.45c0.03,-0.2 0.05,-0.41 0.05,-0.63zM19,12c0,0.94 -0.2,1.82 -0.54,2.64l1.51,1.51C20.63,14.91 21,13.5 21,12c0,-4.28 -2.99,-7.86 -7,-8.77v2.06c2.89,0.86 5,3.54 5,6.71zM4.27,3L3,4.27 7.73,9L3,9v6h4l5,5v-6.73l4.25,4.25c-0.67,0.52 -1.42,0.93 -2.25,1.18v2.06c1.38,-0.31 2.63,-0.95 3.69,-1.81L19.73,21 21,19.73l-9,-9L4.27,3zM12,4L9.91,6.09 12,8.18L12,4z"/>
|
||||
</vector>
|
|
@ -0,0 +1,11 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:autoMirrored="true">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M3,9v6h4l5,5L12,4L7,9L3,9zM16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM14,3.23v2.06c2.89,0.86 5,3.54 5,6.71s-2.11,5.85 -5,6.71v2.06c4.01,-0.91 7,-4.49 7,-8.77s-2.99,-7.86 -7,-8.77z"/>
|
||||
</vector>
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
|
||||
</vector>
|
|
@ -0,0 +1,12 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="256"
|
||||
android:viewportHeight="256"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
>
|
||||
<path
|
||||
android:pathData="M168,128a40,40 0,1 1,-40 -40a40.046,40.046 0,0 1,40 40zM82.745,82.745a8,8 0,1 0,-11.314 -11.313a79.94,79.94 0,0 0,0 113.136a8,8 0,1 0,11.314 -11.313a63.94,63.94 0,0 1,0 -90.51zM208,128a79.777,79.777 0,0 0,-23.43 -56.568a8,8 0,1 0,-11.315 11.313a63.94,63.94 0,0 1,0 90.51a8,8 0,0 0,11.314 11.313A79.777,79.777 0,0 0,208 128zM32.17,168.479A103.904,103.904 0,0 1,54.46 54.461a8,8 0,0 0,-11.314 -11.314a119.906,119.906 0,0 0,0 169.706a8,8 0,1 0,11.315 -11.314a103.651,103.651 0,0 1,-22.291 -33.06zM238.566,81.289a119.581,119.581 0,0 0,-25.712 -38.142a8,8 0,1 0,-11.315 11.314a103.905,103.905 0,0 1,0 147.078a8,8 0,0 0,11.315 11.314a120.121,120.121 0,0 0,25.712 -131.565z"
|
||||
android:fillColor="@android:color/white"
|
||||
/>
|
||||
</vector>
|
|
@ -0,0 +1,74 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector
|
||||
android:height="108dp"
|
||||
android:width="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
</vector>
|
|
@ -0,0 +1,79 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout>
|
||||
<data/>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="10dp">
|
||||
<ProgressBar
|
||||
android:id="@+id/try_connect"
|
||||
android:layout_gravity="center"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"/>
|
||||
<TextView
|
||||
android:id="@+id/try_connect_msg"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/try_connect"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/error"
|
||||
android:textColor="#FF0000"
|
||||
android:layout_gravity="center"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/instance_title"
|
||||
android:text="@string/instance"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
<EditText
|
||||
android:id="@+id/instance"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
<TextView
|
||||
android:id="@+id/error_instance"
|
||||
android:textColor="#FF0000"
|
||||
android:layout_gravity="center"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/instance_error"/>
|
||||
<TextView
|
||||
android:id="@+id/username_title"
|
||||
android:text="@string/username"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
<EditText
|
||||
android:id="@+id/username"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
<TextView
|
||||
android:id="@+id/error_username"
|
||||
android:textColor="#FF0000"
|
||||
android:layout_gravity="center"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/username_error"/>
|
||||
<TextView
|
||||
android:id="@+id/password_title"
|
||||
android:text="@string/password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
<EditText
|
||||
android:id="@+id/password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textPassword"/>
|
||||
<TextView
|
||||
android:id="@+id/error_password"
|
||||
android:textColor="#FF0000"
|
||||
android:layout_gravity="center"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/password_error"/>
|
||||
|
||||
</LinearLayout>
|
||||
</layout>
|
|
@ -0,0 +1,307 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<data/>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingBottom="100dp"
|
||||
android:clipToPadding="false">
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/loading_progress"
|
||||
android:layout_gravity="center"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/loading_channels"
|
||||
/>
|
||||
<TextView
|
||||
android:id="@+id/loading_channels"
|
||||
android:gravity="center"
|
||||
android:text="@string/loading_channels"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/error"
|
||||
android:textColor="#FF0000"
|
||||
android:layout_gravity="center"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_margin="5dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:text="@string/stream_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/error"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_margin="5dp"/>
|
||||
<EditText
|
||||
android:id="@+id/live_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/title"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_margin="5dp"
|
||||
android:singleLine="true"/>
|
||||
<TextView
|
||||
android:id="@+id/title_error"
|
||||
android:text="@string/stream_title_error"
|
||||
android:textColor="#FF0000"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/live_title"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_margin="5dp"/>
|
||||
<TextView
|
||||
android:id="@+id/channel"
|
||||
android:text="@string/choose_channel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/title_error"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_margin="5dp"/>
|
||||
<Spinner
|
||||
android:id="@+id/channel_list"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/channel"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_margin="5dp"
|
||||
/>
|
||||
<TextView
|
||||
android:id="@+id/privacy"
|
||||
android:text="@string/stream_privacy"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/channel_list"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_margin="5dp"/>
|
||||
<Spinner
|
||||
android:id="@+id/privacy_list"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/privacy"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_margin="5dp"
|
||||
/>
|
||||
<TextView
|
||||
android:id="@+id/advance_settings"
|
||||
android:text="@string/advanced_settings"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/privacy_list"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_margin="5dp"/>
|
||||
<TextView
|
||||
android:id="@+id/category"
|
||||
android:text="@string/stream_category"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/advance_settings"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_margin="5dp"/>
|
||||
<Spinner
|
||||
android:id="@+id/category_list"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/category"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_margin="5dp"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/licence"
|
||||
android:text="@string/stream_licence"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/category_list"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_margin="5dp"/>
|
||||
<Spinner
|
||||
android:id="@+id/licence_list"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/licence"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_margin="5dp"
|
||||
/>
|
||||
<TextView
|
||||
android:id="@+id/language"
|
||||
android:text="@string/stream_language"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/licence_list"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_margin="5dp"/>
|
||||
<Spinner
|
||||
android:id="@+id/language_list"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/language"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_margin="5dp"
|
||||
/>
|
||||
<TextView
|
||||
android:id="@+id/description_title"
|
||||
android:text="@string/description"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/language_list"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_margin="5dp"/>
|
||||
<EditText
|
||||
android:id="@+id/description"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="150dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/description_title"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_margin="5dp"
|
||||
android:inputType="textMultiLine"
|
||||
android:lines="4"
|
||||
android:gravity="top|start"
|
||||
/>
|
||||
<TextView
|
||||
android:id="@+id/comments_enabled_title"
|
||||
android:text="@string/comments_enabled"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/description"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/comments_enabled"
|
||||
app:layout_constraintBottom_toBottomOf="@id/comments_enabled"
|
||||
android:layout_margin="5dp"/>
|
||||
<androidx.appcompat.widget.AppCompatCheckBox
|
||||
android:id="@+id/comments_enabled"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/description"
|
||||
app:layout_constraintStart_toEndOf="@id/comments_enabled_title"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_margin="5dp"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/download_enabled_title"
|
||||
android:text="@string/download_enabled"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/comments_enabled_title"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/download_enabled"
|
||||
app:layout_constraintBottom_toBottomOf="@id/download_enabled"
|
||||
android:layout_margin="5dp"/>
|
||||
<androidx.appcompat.widget.AppCompatCheckBox
|
||||
android:id="@+id/download_enabled"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/comments_enabled_title"
|
||||
app:layout_constraintStart_toEndOf="@id/download_enabled_title"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_margin="5dp"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/nsfw_title"
|
||||
android:text="@string/nsfw"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/download_enabled_title"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/nsfw"
|
||||
app:layout_constraintBottom_toBottomOf="@id/nsfw"
|
||||
android:layout_margin="5dp"/>
|
||||
<androidx.appcompat.widget.AppCompatCheckBox
|
||||
android:id="@+id/nsfw"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/download_enabled_title"
|
||||
app:layout_constraintStart_toEndOf="@id/nsfw_title"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_margin="5dp"
|
||||
/>
|
||||
<TextView
|
||||
android:id="@+id/save_replay_title"
|
||||
android:text="@string/save_replay"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/nsfw_title"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/save_replay"
|
||||
app:layout_constraintBottom_toBottomOf="@id/save_replay"
|
||||
android:layout_margin="5dp"/>
|
||||
<androidx.appcompat.widget.AppCompatCheckBox
|
||||
android:id="@+id/save_replay"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/nsfw_title"
|
||||
app:layout_constraintStart_toEndOf="@id/save_replay_title"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_margin="5dp"
|
||||
/>
|
||||
<TextView
|
||||
android:id="@+id/save_replay_info"
|
||||
android:text="@string/save_replay_info"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/save_replay_title"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_margin="5dp"/>
|
||||
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</ScrollView>
|
||||
<com.google.android.material.bottomappbar.BottomAppBar
|
||||
android:id="@+id/bottom_bar"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
style="@style/Widget.MaterialComponents.BottomAppBar.Colored"
|
||||
android:layout_gravity="bottom"
|
||||
/>
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/go_live"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:srcCompat="@drawable/ic_baseline_broadcast_24"
|
||||
app:layout_anchor="@id/bottom_bar"
|
||||
/>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</layout>
|
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<data/>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".fragment.home.HomeFragment">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/instance_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
/>
|
||||
<TextView
|
||||
android:id="@+id/no_instance"
|
||||
android:text="@string/no_instance"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
/>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</layout>
|
|
@ -0,0 +1,46 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<data/>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_margin="5dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp">
|
||||
|
||||
<TextView
|
||||
android:text="Schoumi"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
android:id="@+id/username"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/url"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"/>
|
||||
<TextView
|
||||
android:text="https://peertube2.cpy.re"
|
||||
android:id="@+id/url"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/separator"
|
||||
app:layout_constraintTop_toBottomOf="@id/username"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"/>
|
||||
<TextView
|
||||
android:id="@+id/separator"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
style="?android:attr/listSeparatorTextViewStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="5dp"/>
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:srcCompat="@drawable/baseline_navigate_next_24"
|
||||
/>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</layout>
|
|
@ -0,0 +1,92 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<data/>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#000000"
|
||||
android:keepScreenOn="true"
|
||||
>
|
||||
<TextView
|
||||
android:layout_margin="5dp"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/permission_info"
|
||||
android:text="@string/permissions"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:gravity="center"
|
||||
/>
|
||||
<TextView
|
||||
android:layout_margin="5dp"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/goto_permission"
|
||||
android:text="@string/goto_permissions"
|
||||
app:layout_constraintTop_toBottomOf="@id/permission_info"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:gravity="center"
|
||||
android:textColor="#0000FF"
|
||||
/>
|
||||
<com.pedro.rtplibrary.view.OpenGlView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:id="@+id/surfaceView"
|
||||
app:keepAspectRatio="true"
|
||||
app:aspectRatioMode="adjust_rotate"
|
||||
app:AAEnabled="false"
|
||||
app:numFilters="1"
|
||||
app:isFlipHorizontal="false"
|
||||
app:isFlipVertical="false"
|
||||
app:layout_constraintTop_toBottomOf="@id/mute_micro"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_marginTop="15dp"
|
||||
/>
|
||||
<ImageView
|
||||
android:layout_marginTop="15dp"
|
||||
android:id="@+id/rotation"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
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" />
|
||||
<ImageView
|
||||
android:layout_marginTop="15dp"
|
||||
android:id="@+id/flash"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
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" />
|
||||
<ImageView
|
||||
android:layout_marginTop="15dp"
|
||||
android:id="@+id/mute_micro"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
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" />
|
||||
<ImageView
|
||||
android:layout_marginTop="15dp"
|
||||
android:id="@+id/switch_camera"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
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" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</layout>
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/add_instance"
|
||||
android:icon="@drawable/ic_baseline_add_24"
|
||||
android:title="@string/add_instance"
|
||||
android:visible="true"
|
||||
app:showAsAction="always" />
|
||||
</menu>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 58 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 116 KiB |
After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 55 KiB |
After Width: | Height: | Size: 190 KiB |
After Width: | Height: | Size: 64 KiB |
|
@ -0,0 +1,289 @@
|
|||
<resources>
|
||||
<string name="app_name">Peertube Live</string>
|
||||
|
||||
<!-- errors -->
|
||||
<string name="network_error">Aucune connexion internet détecté</string>
|
||||
<string name="unknwon_error">Error Inconnu</string>
|
||||
<string name="unknown_host">Hôte Inconnu</string>
|
||||
<string name="json_error">Erreur JSON</string>
|
||||
<string name="stream_title_error">Le titre ne peut pas être vide</string>
|
||||
<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="malformed_instance_error">L\'instance doit avoir une url valide</string>
|
||||
<string name="account_exist">Ce compte existe déjà</string>
|
||||
|
||||
<!-- messages -->
|
||||
<string name="no_instance">Aucun compte enregistré. Pour ajouter un compte peertube, cliquez sur le \'+\' dans la top bar</string>
|
||||
<string name="loading_channels">Chargement de la liste de vos chaîne</string>
|
||||
<string name="try_connect">En attente de connexion</string>
|
||||
<string name="permissions">Nous avons besoin d\'accéder à la caméra et au micro pour le direct. Pour changer les paramètres de permission, cliquez en dessous</string>
|
||||
<string name="delete_account">Êtes vous sure de vouloir supprimer le compte %s associé au serveur %s ?</string>
|
||||
<string name="tags_rules">Maximum 5 tags, Chacun entre 2 et 30 caractères, séparé par une virgule</string>
|
||||
<string name="save_replay_info">Si vous activez cette option, votre direct sera arrêté si vous dépassez votre quota vidéo</string>
|
||||
<string name="back_reason">Direct terminé après que vous ayez appuyé sur la touche retour</string>
|
||||
<string name="background_reason">Direct terminé car l\'application est passé à l\'arrière plan</string>
|
||||
<string name="lock_reason">Direct terminé car le téléphone a été verrouillé</string>
|
||||
<string name="ask_end_stream">Voulez vous arrêter le direct?</string>
|
||||
|
||||
|
||||
<!-- buttons -->
|
||||
<string name="choose_channel">Chaîne</string>
|
||||
<string name="go_live">Démarrer le direct!</string>
|
||||
<string name="cancel">Annuler</string>
|
||||
<string name="yes">Oui</string>
|
||||
<string name="no">Non</string>
|
||||
<string name="goto_permissions">Aller dans les paramètres</string>
|
||||
<string name="connect">Connecter</string>
|
||||
|
||||
<!-- titles -->
|
||||
<string name="stream_title">Titre</string>
|
||||
<string name="stream_category">Categorie</string>
|
||||
<string name="stream_privacy">Visibilité</string>
|
||||
<string name="stream_language">Langue</string>
|
||||
<string name="stream_licence">Licence</string>
|
||||
<string name="advanced_settings">▶ Paramètres Avancées</string>
|
||||
<string name="advanced_settings_expand">▼ Paramètres Avancées</string>
|
||||
<string name="add_instance">Ajouter ce compte</string>
|
||||
<string name="username">Nom d\'utilisateur</string>
|
||||
<string name="password">Mot de passe</string>
|
||||
<string name="instance">Instance</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>
|
||||
<string name="nsfw">Contient du contenu sensible</string>
|
||||
<string name="tags">Tags</string>
|
||||
<string name="description">Description</string>
|
||||
<string name="save_replay">Publier une rediffusion automatiquement à la fin du direct</string>
|
||||
<string name="stream_ended">Direct terminé</string>
|
||||
<string name="end_stream">Arrêter le direct</string>
|
||||
|
||||
<!-- category -->
|
||||
<string name="music">Musiques</string>
|
||||
<string name="films">Films</string>
|
||||
<string name="vehicles">Vehicules</string>
|
||||
<string name="art">Art</string>
|
||||
<string name="sports">Sports</string>
|
||||
<string name="travels">Voyages</string>
|
||||
<string name="gaming">Jeux Vidéos</string>
|
||||
<string name="people">Personnalités</string>
|
||||
<string name="comedy">Humour</string>
|
||||
<string name="entertainment">Divertissement</string>
|
||||
<string name="news_politics">Actualité & Politique</string>
|
||||
<string name="how_to">Tutoriels</string>
|
||||
<string name="education">Éducation</string>
|
||||
<string name="activism">Militantisme</string>
|
||||
<string name="science_tech">Science & Technologie</string>
|
||||
<string name="animals">Animaux</string>
|
||||
<string name="kids">Enfants</string>
|
||||
<string name="food">Cuisine</string>
|
||||
|
||||
<!-- language --><string name="afar">Afar</string>
|
||||
<string name="abkhazian">Abkhaze</string>
|
||||
<string name="afrikaans">Afrikaans</string>
|
||||
<string name="akan">Akan</string>
|
||||
<string name="amharic">Amharique</string>
|
||||
<string name="arabic">Arabe</string>
|
||||
<string name="aragonese">Aragonais</string>
|
||||
<string name="american_sign_language">Langue des signes américaine</string>
|
||||
<string name="assamese">Assamais</string>
|
||||
<string name="avaric">Avar</string>
|
||||
<string name="kotava">Kotava</string>
|
||||
<string name="aymara">Aymara</string>
|
||||
<string name="azerbaijani">Azéri</string>
|
||||
<string name="bashkir">Bachkir</string>
|
||||
<string name="bambara">Bambara</string>
|
||||
<string name="belarusian">Biélorusse</string>
|
||||
<string name="bengali">Bengali</string>
|
||||
<string name="british_sign_language">Langue des signes britannique</string>
|
||||
<string name="bislama">Bichlamar</string>
|
||||
<string name="tibetan">Tibétain</string>
|
||||
<string name="bosnian">Bosniaque</string>
|
||||
<string name="breton">Breton</string>
|
||||
<string name="bulgarian">Bulgare</string>
|
||||
<string name="brazilian_sign_language">Langue des signes brésilienne</string>
|
||||
<string name="catalan">Catalan</string>
|
||||
<string name="czech">Tchèque</string>
|
||||
<string name="chamorro">Chamorro</string>
|
||||
<string name="chechen">Tchétchène</string>
|
||||
<string name="chuvash">Tchouvache</string>
|
||||
<string name="cornish">Cornique</string>
|
||||
<string name="corsican">Corse</string>
|
||||
<string name="cree">Cree</string>
|
||||
<string name="czech_sign_language">Langue des signes tchèque</string>
|
||||
<string name="chinese_sign_language">Langue des signes chinoise</string>
|
||||
<string name="welsh">Gallois</string>
|
||||
<string name="danish">Danois</string>
|
||||
<string name="german">Allemand</string>
|
||||
<string name="dhivehi">Maldivien</string>
|
||||
<string name="danish_sign_language">Langue des signes danoise</string>
|
||||
<string name="dzongkha">Dzongkha</string>
|
||||
<string name="greek">Grec</string>
|
||||
<string name="english">Anglais</string>
|
||||
<string name="esperanto">Espéranto</string>
|
||||
<string name="estonian">Estonien</string>
|
||||
<string name="basque">Basque</string>
|
||||
<string name="ewe">Éwé</string>
|
||||
<string name="faroese">Féroïen</string>
|
||||
<string name="persian">Persan</string>
|
||||
<string name="fijian">Fidjien</string>
|
||||
<string name="finnish">Finnois</string>
|
||||
<string name="french">Français</string>
|
||||
<string name="western_frisian">Frison occidental</string>
|
||||
<string name="french_sign_language">Langue des signes française</string>
|
||||
<string name="fulah">Peul</string>
|
||||
<string name="scottish_gaelic">Gaélique</string>
|
||||
<string name="irish">Irlandais</string>
|
||||
<string name="galician">Galicien</string>
|
||||
<string name="manx">Manx</string>
|
||||
<string name="guarani">Guarani</string>
|
||||
<string name="german_sign_language">Langue des signes allemande</string>
|
||||
<string name="gujarati">Goudjrati</string>
|
||||
<string name="haitian">Haïtien</string>
|
||||
<string name="hausa">Haoussa</string>
|
||||
<string name="serbo_croatian">Serbo-croate</string>
|
||||
<string name="hebrew">Hébreu</string>
|
||||
<string name="herero">Herero</string>
|
||||
<string name="hindi">Hindi</string>
|
||||
<string name="hiri_motu">Hiri motu</string>
|
||||
<string name="croatian">Croate</string>
|
||||
<string name="hungarian">Hongrois</string>
|
||||
<string name="armenian">Arménien</string>
|
||||
<string name="igbo">Igbo</string>
|
||||
<string name="sichuan_yi">Yi de Sichuan</string>
|
||||
<string name="inuktitut">Inuktitut</string>
|
||||
<string name="indonesian">Indonésien</string>
|
||||
<string name="inupiaq">Inupiaq</string>
|
||||
<string name="icelandic">Islandais</string>
|
||||
<string name="italian">Italien</string>
|
||||
<string name="javanese">Javanais</string>
|
||||
<string name="lojban">Lojban</string>
|
||||
<string name="japanese">Japonais</string>
|
||||
<string name="japanese_sign_language">Langue des signes japonaise</string>
|
||||
<string name="kabyle">Kabyle</string>
|
||||
<string name="kalaallisut">Groenlandais</string>
|
||||
<string name="kannada">Kannada</string>
|
||||
<string name="kashmiri">Kashmiri</string>
|
||||
<string name="georgian">Géorgien</string>
|
||||
<string name="kanuri">Kanouri</string>
|
||||
<string name="kazakh">Kazakh</string>
|
||||
<string name="khmer">Khmer central</string>
|
||||
<string name="kikuyu">Kikuyu</string>
|
||||
<string name="kinyarwanda">Rwanda</string>
|
||||
<string name="kirghiz">Kirghiz</string>
|
||||
<string name="komi">Kom</string>
|
||||
<string name="kongo">Kongo</string>
|
||||
<string name="korean">Coréen</string>
|
||||
<string name="kuanyama">Kuanyama</string>
|
||||
<string name="kurdish">Kurde</string>
|
||||
<string name="lao">Lao</string>
|
||||
<string name="latvian">Letton</string>
|
||||
<string name="limburgan">Limbourgeois</string>
|
||||
<string name="lingala">Lingala</string>
|
||||
<string name="lithuanian">Lituanien</string>
|
||||
<string name="luxembourgish">Luxembourgeois</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">Marathe</string>
|
||||
<string name="macedonian">Macédonien</string>
|
||||
<string name="malagasy">Malgache</string>
|
||||
<string name="maltese">Maltais</string>
|
||||
<string name="mongolian">Mongol</string>
|
||||
<string name="maori">Maori</string>
|
||||
<string name="malay_macrolanguage">Malais</string>
|
||||
<string name="burmese">Birman</string>
|
||||
<string name="nauru">Nauruan</string>
|
||||
<string name="navajo">Navaho</string>
|
||||
<string name="south_ndebele">Ndébélé du Sud</string>
|
||||
<string name="north_ndebele">Ndébélé du Nord</string>
|
||||
<string name="ndonga">Ndonga</string>
|
||||
<string name="nepali_macrolanguage">Népalais</string>
|
||||
<string name="dutch">Néerlandais</string>
|
||||
<string name="norwegian_nynorsk">Norvégien nynorsk</string>
|
||||
<string name="norwegian_bokmål">Norvégien bokmål</string>
|
||||
<string name="norwegian">Norvégien</string>
|
||||
<string name="nyanja">Chichewa</string>
|
||||
<string name="occitan">Occitane</string>
|
||||
<string name="ojibwa">Ojibwa</string>
|
||||
<string name="oriya_macrolanguage">Oriya</string>
|
||||
<string name="oromo">Galla</string>
|
||||
<string name="ossetian">Ossète</string>
|
||||
<string name="panjabi">Pendjabi</string>
|
||||
<string name="pakistan_sign_language">Langue des signes pakistanaise</string>
|
||||
<string name="polish">Polonais</string>
|
||||
<string name="portuguese">Portugais</string>
|
||||
<string name="pushto">Pachto</string>
|
||||
<string name="quechua">Quechua</string>
|
||||
<string name="romansh">Romanche</string>
|
||||
<string name="romanian">Roumain</string>
|
||||
<string name="russian_sign_language">Langue des signes russe</string>
|
||||
<string name="rundi">Rundi</string>
|
||||
<string name="russian">Russe</string>
|
||||
<string name="sango">Sango</string>
|
||||
<string name="saudi_arabian_sign_language">Langue des signes saoudienne</string>
|
||||
<string name="south_african_sign_language">Langue des signes sud-africaine</string>
|
||||
<string name="sinhala">Singhalais</string>
|
||||
<string name="slovak">Slovaque</string>
|
||||
<string name="slovenian">Slovène</string>
|
||||
<string name="northern_sami">Sami du Nord</string>
|
||||
<string name="samoan">Samoan</string>
|
||||
<string name="shona">Shona</string>
|
||||
<string name="sindhi">Sindhi</string>
|
||||
<string name="somali">Somali</string>
|
||||
<string name="southern_sotho">Sotho du Sud</string>
|
||||
<string name="spanish">Espagnol</string>
|
||||
<string name="albanian">Albanais</string>
|
||||
<string name="sardinian">Sarde</string>
|
||||
<string name="serbian">Serbe</string>
|
||||
<string name="swati">Swati</string>
|
||||
<string name="sundanese">Soundanais</string>
|
||||
<string name="swahili_macrolanguage">Swahili</string>
|
||||
<string name="swedish">Suédois</string>
|
||||
<string name="swedish_sign_language">Langue des signes suédoise</string>
|
||||
<string name="tahitian">Tahitien</string>
|
||||
<string name="tamil">Tamoul</string>
|
||||
<string name="tatar">Tatar</string>
|
||||
<string name="telugu">Télougou</string>
|
||||
<string name="tajik">Tadjik</string>
|
||||
<string name="tagalog">Tagalog</string>
|
||||
<string name="thai">Thaï</string>
|
||||
<string name="tigrinya">Tigrigna</string>
|
||||
<string name="klingon">Klingon</string>
|
||||
<string name="tonga_tonga_islands">Tongan (Îles Tonga)</string>
|
||||
<string name="tswana">Tswana</string>
|
||||
<string name="tsonga">Tsonga</string>
|
||||
<string name="turkmen">Turkmène</string>
|
||||
<string name="turkish">Turc</string>
|
||||
<string name="twi">Twi</string>
|
||||
<string name="uighur">Ouïgour</string>
|
||||
<string name="ukrainian">Ukrainien</string>
|
||||
<string name="urdu">Ourdou</string>
|
||||
<string name="uzbek">Ouszbek</string>
|
||||
<string name="venda">Venda</string>
|
||||
<string name="vietnamese">Vietnamien</string>
|
||||
<string name="walloon">Wallon</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">Chinois</string>
|
||||
<string name="zulu">Zoulou</string>
|
||||
|
||||
<!-- licence -->
|
||||
<string name="by">Attribution</string>>
|
||||
<string name="bysa">Attribution - Partage dans les mêmes conditions</string>
|
||||
<string name="bynd">Attribution - Pas d\'œuvre dérivée</string>
|
||||
<string name="bync">Attribution - Utilisation non commerciale</string>
|
||||
<string name="byncsa">Attribution - Utilisation non commerciale - Partage dans les mêmes conditions</string>
|
||||
<string name="byncnd">Attribution - Utilisation non commerciale - Pas d\'œuvre dérivée</string>
|
||||
<string name="public_domain">Domaine Publique</string>
|
||||
|
||||
<!-- privacies -->
|
||||
<string name="privacy_public">Publique</string>
|
||||
<string name="unlisted">Non Listée</string>
|
||||
<string name="privacy_private">Privée</string>
|
||||
<string name="internal">Interne</string>
|
||||
</resources>
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<resources>
|
||||
<color name="colorPrimary">#202020</color>
|
||||
<color name="colorPrimaryDark">#202020</color>
|
||||
<color name="colorSecondary">#F2690D</color>
|
||||
<color name="colorOnSecondary">#000000</color>
|
||||
<color name="white">#FFFFFF</color>
|
||||
</resources>
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<resources>
|
||||
<color name="colorPrimary">#F2690D</color>
|
||||
<color name="colorPrimaryDark">#B83900</color>
|
||||
<color name="colorSecondary">#202020</color>
|
||||
<color name="colorOnSecondary">#FFFFFF</color>
|
||||
<color name="white">#FFFFFF</color>
|
||||
</resources>
|
||||
|
||||
<!--
|
||||
<resources>
|
||||
<color name="colorPrimary">#202020</color>
|
||||
<color name="colorPrimaryDark">#111111</color>
|
||||
<color name="colorSecondary">#F2690D</color>
|
||||
<color name="colorOnSecondary">#000000</color>
|
||||
<color name="white">#FFFFFF</color>
|
||||
</resources>
|
||||
-->
|
|
@ -0,0 +1,3 @@
|
|||
<resources>
|
||||
<dimen name="fab_margin">16dp</dimen>
|
||||
</resources>
|
|
@ -0,0 +1,289 @@
|
|||
<resources>
|
||||
<string name="app_name">Peertube Live</string>
|
||||
|
||||
<!-- errors -->
|
||||
<string name="network_error">No Internet Connection Found</string>
|
||||
<string name="unknwon_error">Unknown Error</string>
|
||||
<string name="unknown_host">Unknown Host</string>
|
||||
<string name="json_error">JSON Error</string>
|
||||
<string name="stream_title_error">Title should not be empty</string>
|
||||
<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="malformed_instance_error">Instance should be a valid url</string>
|
||||
<string name="account_exist">This account already exist</string>
|
||||
|
||||
<!-- messages -->
|
||||
<string name="no_instance">No account registered. To add a peertube account, click on the \'+\' in the top bar</string>
|
||||
<string name="loading_channels">Loading your channel list</string>
|
||||
<string name="try_connect">Waiting for connection</string>
|
||||
<string name="permissions">We need the access to the camera and microphone to live stream. To change permissions settings click below</string>
|
||||
<string name="delete_account">Are you sure you want to delete the account %s associated with the server %s ?</string>
|
||||
<string name="tags_rules">Maximum 5 tags, each between 2 and 30 characters, separate by comma</string>
|
||||
<string name="save_replay_info">If you enable this option, your live will be terminated if you exceed your video quota</string>
|
||||
<string name="back_reason">Live has stop after you pressed back button</string>
|
||||
<string name="background_reason">Live has stop because the app has gone to background</string>
|
||||
<string name="lock_reason">Live has stop because the phone was locked</string>
|
||||
<string name="ask_end_stream">Do you want to stop the live?</string>
|
||||
|
||||
<!-- buttons -->
|
||||
<string name="choose_channel">Channel</string>
|
||||
<string name="go_live">Go Live!</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
<string name="yes">Yes</string>
|
||||
<string name="no">No</string>
|
||||
<string name="goto_permissions">View settings</string>
|
||||
<string name="connect">Connect</string>
|
||||
|
||||
<!-- titles -->
|
||||
<string name="stream_title">Title</string>
|
||||
<string name="stream_category">Category</string>
|
||||
<string name="stream_privacy">Privacy</string>
|
||||
<string name="stream_language">Language</string>
|
||||
<string name="stream_licence">Licence</string>
|
||||
<string name="advanced_settings">▶ Advanced Settings</string>
|
||||
<string name="advanced_settings_expand">▼ Advanced Settings</string>
|
||||
<string name="add_instance">Add this account</string>
|
||||
<string name="username">Username</string>
|
||||
<string name="password">Password</string>
|
||||
<string name="instance">Instance</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>
|
||||
<string name="nsfw">Contains sensitive content</string>
|
||||
<string name="tags">Tags</string>
|
||||
<string name="description">Description</string>
|
||||
<string name="save_replay">Automatically publish a replay when your live ends</string>
|
||||
<string name="stream_ended">Live ended</string>
|
||||
<string name="end_stream">Stop the live</string>
|
||||
|
||||
<!-- category -->
|
||||
<string name="music">Music</string>
|
||||
<string name="films">Films</string>
|
||||
<string name="vehicles">Vehicles</string>
|
||||
<string name="art">Art</string>
|
||||
<string name="sports">Sports</string>
|
||||
<string name="travels">Travels</string>
|
||||
<string name="gaming">Gaming</string>
|
||||
<string name="people">People</string>
|
||||
<string name="comedy">Comedy</string>
|
||||
<string name="entertainment">Entertainment</string>
|
||||
<string name="news_politics">News & Politics</string>
|
||||
<string name="how_to">How To</string>
|
||||
<string name="education">Education</string>
|
||||
<string name="activism">Activism</string>
|
||||
<string name="science_tech">Science & Technology</string>
|
||||
<string name="animals">Animals</string>
|
||||
<string name="kids">Kids</string>
|
||||
<string name="food">Food</string>
|
||||
|
||||
<!-- language -->
|
||||
<string name="afar">Afar</string>
|
||||
<string name="abkhazian">Abkhazian</string>
|
||||
<string name="afrikaans">Afrikaans</string>
|
||||
<string name="akan">Akan</string>
|
||||
<string name="amharic">Amharic</string>
|
||||
<string name="arabic">Arabic</string>
|
||||
<string name="aragonese">Aragonese</string>
|
||||
<string name="american_sign_language">American Sign Language</string>
|
||||
<string name="assamese">Assamese</string>
|
||||
<string name="avaric">Avaric</string>
|
||||
<string name="kotava">Kotava</string>
|
||||
<string name="aymara">Aymara</string>
|
||||
<string name="azerbaijani">Azerbaijani</string>
|
||||
<string name="bashkir">Bashkir</string>
|
||||
<string name="bambara">Bambara</string>
|
||||
<string name="belarusian">Belarusian</string>
|
||||
<string name="bengali">Bengali</string>
|
||||
<string name="british_sign_language">British Sign Language</string>
|
||||
<string name="bislama">Bislama</string>
|
||||
<string name="tibetan">Tibetan</string>
|
||||
<string name="bosnian">Bosnian</string>
|
||||
<string name="breton">Breton</string>
|
||||
<string name="bulgarian">Bulgarian</string>
|
||||
<string name="brazilian_sign_language">Brazilian Sign Language</string>
|
||||
<string name="catalan">Catalan</string>
|
||||
<string name="czech">Czech</string>
|
||||
<string name="chamorro">Chamorro</string>
|
||||
<string name="chechen">Chechen</string>
|
||||
<string name="chuvash">Chuvash</string>
|
||||
<string name="cornish">Cornish</string>
|
||||
<string name="corsican">Corsican</string>
|
||||
<string name="cree">Cree</string>
|
||||
<string name="czech_sign_language">Czech Sign Language</string>
|
||||
<string name="chinese_sign_language">Chinese Sign Language</string>
|
||||
<string name="welsh">Welsh</string>
|
||||
<string name="danish">Danish</string>
|
||||
<string name="german">German</string>
|
||||
<string name="dhivehi">Dhivehi</string>
|
||||
<string name="danish_sign_language">Danish Sign Language</string>
|
||||
<string name="dzongkha">Dzongkha</string>
|
||||
<string name="greek">Greek</string>
|
||||
<string name="english">English</string>
|
||||
<string name="esperanto">Esperanto</string>
|
||||
<string name="estonian">Estonian</string>
|
||||
<string name="basque">Basque</string>
|
||||
<string name="ewe">Ewe</string>
|
||||
<string name="faroese">Faroese</string>
|
||||
<string name="persian">Persian</string>
|
||||
<string name="fijian">Fijian</string>
|
||||
<string name="finnish">Finnish</string>
|
||||
<string name="french">French</string>
|
||||
<string name="western_frisian">Western Frisian</string>
|
||||
<string name="french_sign_language">French Sign Language</string>
|
||||
<string name="fulah">Fulah</string>
|
||||
<string name="scottish_gaelic">Scottish Gaelic</string>
|
||||
<string name="irish">Irish</string>
|
||||
<string name="galician">Galician</string>
|
||||
<string name="manx">Manx</string>
|
||||
<string name="guarani">Guarani</string>
|
||||
<string name="german_sign_language">German Sign Language</string>
|
||||
<string name="gujarati">Gujarati</string>
|
||||
<string name="haitian">Haitian</string>
|
||||
<string name="hausa">Hausa</string>
|
||||
<string name="serbo_croatian">Serbo-Croatian</string>
|
||||
<string name="hebrew">Hebrew</string>
|
||||
<string name="herero">Herero</string>
|
||||
<string name="hindi">Hindi</string>
|
||||
<string name="hiri_motu">Hiri Motu</string>
|
||||
<string name="croatian">Croatian</string>
|
||||
<string name="hungarian">Hungarian</string>
|
||||
<string name="armenian">Armenian</string>
|
||||
<string name="igbo">Igbo</string>
|
||||
<string name="sichuan_yi">Sichuan Yi</string>
|
||||
<string name="inuktitut">Inuktitut</string>
|
||||
<string name="indonesian">Indonesian</string>
|
||||
<string name="inupiaq">Inupiaq</string>
|
||||
<string name="icelandic">Icelandic</string>
|
||||
<string name="italian">Italian</string>
|
||||
<string name="javanese">Javanese</string>
|
||||
<string name="lojban">Lojban</string>
|
||||
<string name="japanese">Japanese</string>
|
||||
<string name="japanese_sign_language">Japanese Sign Language</string>
|
||||
<string name="kabyle">Kabyle</string>
|
||||
<string name="kalaallisut">Kalaallisut</string>
|
||||
<string name="kannada">Kannada</string>
|
||||
<string name="kashmiri">Kashmiri</string>
|
||||
<string name="georgian">Georgian</string>
|
||||
<string name="kanuri">Kanuri</string>
|
||||
<string name="kazakh">Kazakh</string>
|
||||
<string name="khmer">Khmer</string>
|
||||
<string name="kikuyu">Kikuyu</string>
|
||||
<string name="kinyarwanda">Kinyarwanda</string>
|
||||
<string name="kirghiz">Kirghiz</string>
|
||||
<string name="komi">Komi</string>
|
||||
<string name="kongo">Kongo</string>
|
||||
<string name="korean">Korean</string>
|
||||
<string name="kuanyama">Kuanyama</string>
|
||||
<string name="kurdish">Kurdish</string>
|
||||
<string name="lao">Lao</string>
|
||||
<string name="latvian">Latvian</string>
|
||||
<string name="limburgan">Limburgan</string>
|
||||
<string name="lingala">Lingala</string>
|
||||
<string name="lithuanian">Lithuanian</string>
|
||||
<string name="luxembourgish">Luxembourgish</string>
|
||||
<string name="luba_katanga">Luba-Katanga</string>
|
||||
<string name="ganda">Ganda</string>
|
||||
<string name="marshallese">Marshallese</string>
|
||||
<string name="malayalam">Malayalam</string>
|
||||
<string name="marathi">Marathi</string>
|
||||
<string name="macedonian">Macedonian</string>
|
||||
<string name="malagasy">Malagasy</string>
|
||||
<string name="maltese">Maltese</string>
|
||||
<string name="mongolian">Mongolian</string>
|
||||
<string name="maori">Maori</string>
|
||||
<string name="malay_macrolanguage">Malay (macrolanguage)</string>
|
||||
<string name="burmese">Burmese</string>
|
||||
<string name="nauru">Nauru</string>
|
||||
<string name="navajo">Navajo</string>
|
||||
<string name="south_ndebele">South Ndebele</string>
|
||||
<string name="north_ndebele">North Ndebele</string>
|
||||
<string name="ndonga">Ndonga</string>
|
||||
<string name="nepali_macrolanguage">Nepali (macrolanguage)</string>
|
||||
<string name="dutch">Dutch</string>
|
||||
<string name="norwegian_nynorsk">Norwegian Nynorsk</string>
|
||||
<string name="norwegian_bokmål">Norwegian Bokmål</string>
|
||||
<string name="norwegian">Norwegian</string>
|
||||
<string name="nyanja">Nyanja</string>
|
||||
<string name="occitan">Occitan</string>
|
||||
<string name="ojibwa">Ojibwa</string>
|
||||
<string name="oriya_macrolanguage">Oriya (macrolanguage)</string>
|
||||
<string name="oromo">Oromo</string>
|
||||
<string name="ossetian">Ossetian</string>
|
||||
<string name="panjabi">Panjabi</string>
|
||||
<string name="pakistan_sign_language">Pakistan Sign Language</string>
|
||||
<string name="polish">Polish</string>
|
||||
<string name="portuguese">Portuguese</string>
|
||||
<string name="pushto">Pushto</string>
|
||||
<string name="quechua">Quechua</string>
|
||||
<string name="romansh">Romansh</string>
|
||||
<string name="romanian">Romanian</string>
|
||||
<string name="russian_sign_language">Russian Sign Language</string>
|
||||
<string name="rundi">Rundi</string>
|
||||
<string name="russian">Russian</string>
|
||||
<string name="sango">Sango</string>
|
||||
<string name="saudi_arabian_sign_language">Saudi Arabian Sign Language</string>
|
||||
<string name="south_african_sign_language">South African Sign Language</string>
|
||||
<string name="sinhala">Sinhala</string>
|
||||
<string name="slovak">Slovak</string>
|
||||
<string name="slovenian">Slovenian</string>
|
||||
<string name="northern_sami">Northern Sami</string>
|
||||
<string name="samoan">Samoan</string>
|
||||
<string name="shona">Shona</string>
|
||||
<string name="sindhi">Sindhi</string>
|
||||
<string name="somali">Somali</string>
|
||||
<string name="southern_sotho">Southern Sotho</string>
|
||||
<string name="spanish">Spanish</string>
|
||||
<string name="albanian">Albanian</string>
|
||||
<string name="sardinian">Sardinian</string>
|
||||
<string name="serbian">Serbian</string>
|
||||
<string name="swati">Swati</string>
|
||||
<string name="sundanese">Sundanese</string>
|
||||
<string name="swahili_macrolanguage">Swahili (macrolanguage)</string>
|
||||
<string name="swedish">Swedish</string>
|
||||
<string name="swedish_sign_language">Swedish Sign Language</string>
|
||||
<string name="tahitian">Tahitian</string>
|
||||
<string name="tamil">Tamil</string>
|
||||
<string name="tatar">Tatar</string>
|
||||
<string name="telugu">Telugu</string>
|
||||
<string name="tajik">Tajik</string>
|
||||
<string name="tagalog">Tagalog</string>
|
||||
<string name="thai">Thai</string>
|
||||
<string name="tigrinya">Tigrinya</string>
|
||||
<string name="klingon">Klingon</string>
|
||||
<string name="tonga_tonga_islands">Tonga (Tonga Islands)</string>
|
||||
<string name="tswana">Tswana</string>
|
||||
<string name="tsonga">Tsonga</string>
|
||||
<string name="turkmen">Turkmen</string>
|
||||
<string name="turkish">Turkish</string>
|
||||
<string name="twi">Twi</string>
|
||||
<string name="uighur">Uighur</string>
|
||||
<string name="ukrainian">Ukrainian</string>
|
||||
<string name="urdu">Urdu</string>
|
||||
<string name="uzbek">Uzbek</string>
|
||||
<string name="venda">Venda</string>
|
||||
<string name="vietnamese">Vietnamese</string>
|
||||
<string name="walloon">Walloon</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">Chinese</string>
|
||||
<string name="zulu">Zulu</string>
|
||||
|
||||
<!-- licence -->
|
||||
<string name="by">Attribution</string>>
|
||||
<string name="bysa">Attribution - Share Alike</string>
|
||||
<string name="bynd">Attribution - No Derivatives</string>
|
||||
<string name="bync">Attribution - Non Commercial</string>
|
||||
<string name="byncsa">Attribution - Non Commercial - Share Alike</string>
|
||||
<string name="byncnd">Attribution - Non Commercial - No Derivatives</string>
|
||||
<string name="public_domain">Public Domain Dedication</string>
|
||||
|
||||
<!-- privacies -->
|
||||
<string name="privacy_public">Public</string>
|
||||
<string name="unlisted">Unlisted</string>
|
||||
<string name="privacy_private">Private</string>
|
||||
<string name="internal">Internal</string>
|
||||
</resources>
|
|
@ -0,0 +1,22 @@
|
|||
<resources>
|
||||
|
||||
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorSecondary">@color/colorSecondary</item>
|
||||
<item name="colorOnSecondary">@color/colorOnSecondary</item>
|
||||
<item name="alertDialogTheme">@style/DialogButton</item>
|
||||
<!--<item name="android:editTextColor">@color/colorAccent</item>-->
|
||||
</style>
|
||||
<style name="DialogButton" parent="Theme.AppCompat.DayNight.Dialog.Alert">
|
||||
<item name="buttonBarNegativeButtonStyle">@style/buttonStyle</item>
|
||||
<item name="buttonBarPositiveButtonStyle">@style/buttonStyle</item>
|
||||
<item name="buttonBarNeutralButtonStyle">@style/buttonStyle</item>
|
||||
</style>
|
||||
|
||||
<style name="buttonStyle" parent="Widget.AppCompat.Button.ButtonBar.AlertDialog">
|
||||
<item name="android:textColor">@color/colorSecondary</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
|
@ -0,0 +1,17 @@
|
|||
package org.framasoft.peertubelive
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.5.10'
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.2.1'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
maven { url 'https://jitpack.io' }
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
/build
|
|
@ -0,0 +1,22 @@
|
|||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 30
|
||||
versionCode 182
|
||||
versionName "1.8.2"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
consumerProguardFiles 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api 'androidx.annotation:annotation:1.2.0'
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in /home/pedro/Android/Sdk/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
|
@ -0,0 +1 @@
|
|||
<manifest package="com.pedro.encoder" />
|
|
@ -0,0 +1,125 @@
|
|||
package com.pedro.encoder;
|
||||
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaCodecInfo;
|
||||
import android.media.MediaFormat;
|
||||
import android.os.Build;
|
||||
import androidx.annotation.NonNull;
|
||||
import com.pedro.encoder.utils.CodecUtil;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* 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 MediaCodec codec;
|
||||
protected long presentTimeUs;
|
||||
protected volatile boolean running = false;
|
||||
protected boolean isBufferMode = true;
|
||||
protected CodecUtil.Force force = CodecUtil.Force.FIRST_COMPATIBLE_FOUND;
|
||||
|
||||
public void start() {
|
||||
start(true);
|
||||
}
|
||||
|
||||
public abstract void start(boolean resetTs);
|
||||
|
||||
protected abstract void stopImp();
|
||||
|
||||
public void stop() {
|
||||
running = false;
|
||||
stopImp();
|
||||
try {
|
||||
codec.stop();
|
||||
codec.release();
|
||||
codec = null;
|
||||
} catch (IllegalStateException | NullPointerException e) {
|
||||
codec = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract MediaCodecInfo chooseEncoder(String mime);
|
||||
|
||||
protected void getDataFromEncoder(Frame frame) throws IllegalStateException {
|
||||
if (isBufferMode) {
|
||||
int inBufferIndex = codec.dequeueInputBuffer(0);
|
||||
if (inBufferIndex >= 0) {
|
||||
inputAvailable(codec, inBufferIndex, frame);
|
||||
}
|
||||
}
|
||||
for (; running; ) {
|
||||
int outBufferIndex = codec.dequeueOutputBuffer(bufferInfo, 0);
|
||||
if (outBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
|
||||
MediaFormat mediaFormat = codec.getOutputFormat();
|
||||
formatChanged(codec, mediaFormat);
|
||||
} else if (outBufferIndex >= 0) {
|
||||
outputAvailable(codec, outBufferIndex, bufferInfo);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Frame getInputFrame() throws InterruptedException;
|
||||
|
||||
private void processInput(@NonNull ByteBuffer byteBuffer, @NonNull MediaCodec mediaCodec,
|
||||
int inBufferIndex, Frame frame) throws IllegalStateException {
|
||||
try {
|
||||
if (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);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void checkBuffer(@NonNull ByteBuffer byteBuffer,
|
||||
@NonNull MediaCodec.BufferInfo bufferInfo);
|
||||
|
||||
protected abstract void sendBuffer(@NonNull ByteBuffer byteBuffer,
|
||||
@NonNull MediaCodec.BufferInfo bufferInfo);
|
||||
|
||||
private void processOutput(@NonNull ByteBuffer byteBuffer, @NonNull MediaCodec mediaCodec,
|
||||
int outBufferIndex, @NonNull MediaCodec.BufferInfo bufferInfo) throws IllegalStateException {
|
||||
checkBuffer(byteBuffer, bufferInfo);
|
||||
sendBuffer(byteBuffer, bufferInfo);
|
||||
mediaCodec.releaseOutputBuffer(outBufferIndex, false);
|
||||
}
|
||||
|
||||
public void setForce(CodecUtil.Force force) {
|
||||
this.force = force;
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return running;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inputAvailable(@NonNull MediaCodec mediaCodec, int inBufferIndex, Frame frame)
|
||||
throws IllegalStateException {
|
||||
ByteBuffer byteBuffer;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
byteBuffer = mediaCodec.getInputBuffer(inBufferIndex);
|
||||
} else {
|
||||
byteBuffer = mediaCodec.getInputBuffers()[inBufferIndex];
|
||||
}
|
||||
processInput(byteBuffer, mediaCodec, inBufferIndex, frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void outputAvailable(@NonNull MediaCodec mediaCodec, int outBufferIndex,
|
||||
@NonNull MediaCodec.BufferInfo bufferInfo) throws IllegalStateException {
|
||||
ByteBuffer byteBuffer;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
byteBuffer = mediaCodec.getOutputBuffer(outBufferIndex);
|
||||
} else {
|
||||
byteBuffer = mediaCodec.getOutputBuffers()[outBufferIndex];
|
||||
}
|
||||
processOutput(byteBuffer, mediaCodec, outBufferIndex, bufferInfo);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package com.pedro.encoder;
|
||||
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaFormat;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Created by pedro on 18/09/19.
|
||||
*/
|
||||
public interface EncoderCallback {
|
||||
void inputAvailable(@NonNull MediaCodec mediaCodec, int inBufferIndex, Frame frame)
|
||||
throws IllegalStateException;
|
||||
|
||||
void outputAvailable(@NonNull MediaCodec mediaCodec, int outBufferIndex,
|
||||
@NonNull MediaCodec.BufferInfo bufferInfo) throws IllegalStateException;
|
||||
|
||||
void formatChanged(@NonNull MediaCodec mediaCodec, @NonNull MediaFormat mediaFormat);
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package com.pedro.encoder;
|
||||
|
||||
import android.graphics.ImageFormat;
|
||||
|
||||
/**
|
||||
* Created by pedro on 17/02/18.
|
||||
*/
|
||||
|
||||
public class Frame {
|
||||
|
||||
private byte[] buffer;
|
||||
private int offset;
|
||||
private int size;
|
||||
private int orientation;
|
||||
private boolean flip;
|
||||
private int format = ImageFormat.NV21; //nv21 or yv12 supported
|
||||
|
||||
/**
|
||||
* Used with video frame
|
||||
*/
|
||||
public Frame(byte[] buffer, int orientation, boolean flip, int format) {
|
||||
this.buffer = buffer;
|
||||
this.orientation = orientation;
|
||||
this.flip = flip;
|
||||
this.format = format;
|
||||
offset = 0;
|
||||
size = buffer.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used with audio frame
|
||||
*/
|
||||
public Frame(byte[] buffer, int offset, int size) {
|
||||
this.buffer = buffer;
|
||||
this.offset = offset;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public byte[] getBuffer() {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public void setBuffer(byte[] buffer) {
|
||||
this.buffer = buffer;
|
||||
}
|
||||
|
||||
public int getOrientation() {
|
||||
return orientation;
|
||||
}
|
||||
|
||||
public void setOrientation(int orientation) {
|
||||
this.orientation = orientation;
|
||||
}
|
||||
|
||||
public boolean isFlip() {
|
||||
return flip;
|
||||
}
|
||||
|
||||
public void setFlip(boolean flip) {
|
||||
this.flip = flip;
|
||||
}
|
||||
|
||||
public int getFormat() {
|
||||
return format;
|
||||
}
|
||||
|
||||
public void setFormat(int format) {
|
||||
this.format = format;
|
||||
}
|
||||
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public void setOffset(int offset) {
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public void setSize(int size) {
|
||||
this.size = size;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
package com.pedro.encoder.audio;
|
||||
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaCodecInfo;
|
||||
import android.media.MediaFormat;
|
||||
import android.util.Log;
|
||||
import androidx.annotation.NonNull;
|
||||
import com.pedro.encoder.BaseEncoder;
|
||||
import com.pedro.encoder.Frame;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Created by pedro on 19/01/17.
|
||||
*
|
||||
* Encode PCM audio data to ACC and return in a callback
|
||||
*/
|
||||
|
||||
public class AudioEncoder extends BaseEncoder implements GetMicrophoneData {
|
||||
|
||||
private static final String TAG = "AudioEncoder";
|
||||
|
||||
private GetAacData getAacData;
|
||||
private int bitRate = 64 * 1024; //in kbps
|
||||
private int sampleRate = 32000; //in hz
|
||||
private boolean isStereo = true;
|
||||
|
||||
public AudioEncoder(GetAacData getAacData) {
|
||||
this.getAacData = getAacData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare encoder with custom parameters
|
||||
*/
|
||||
public boolean prepareAudioEncoder(int bitRate, int sampleRate, boolean isStereo,
|
||||
int maxInputSize) {
|
||||
this.sampleRate = sampleRate;
|
||||
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;
|
||||
}
|
||||
} else {
|
||||
if (encoders.isEmpty()) {
|
||||
Log.e(TAG, "Valid encoder not found");
|
||||
return false;
|
||||
} else {
|
||||
codec = MediaCodec.createByCodecName(encoders.get(0).getName());
|
||||
}
|
||||
}
|
||||
|
||||
int channelCount = (isStereo) ? 2 : 1;
|
||||
MediaFormat audioFormat =
|
||||
MediaFormat.createAudioFormat(CodecUtil.AAC_MIME, sampleRate, channelCount);
|
||||
audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
|
||||
audioFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize);
|
||||
audioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE,
|
||||
MediaCodecInfo.CodecProfileLevel.AACObjectLC);
|
||||
codec.configure(audioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
|
||||
running = false;
|
||||
Log.i(TAG, "prepared");
|
||||
return true;
|
||||
} catch (IOException | IllegalStateException e) {
|
||||
Log.e(TAG, "Create AudioEncoder failed.", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare encoder with default parameters
|
||||
*/
|
||||
public boolean prepareAudioEncoder() {
|
||||
return prepareAudioEncoder(bitRate, sampleRate, isStereo, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(boolean resetTs) {
|
||||
presentTimeUs = System.nanoTime() / 1000;
|
||||
codec.start();
|
||||
running = true;
|
||||
Log.i(TAG, "started");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void stopImp() {
|
||||
Log.i(TAG, "stopped");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Frame getInputFrame() throws InterruptedException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void checkBuffer(@NonNull ByteBuffer byteBuffer,
|
||||
@NonNull MediaCodec.BufferInfo bufferInfo) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sendBuffer(@NonNull ByteBuffer byteBuffer,
|
||||
@NonNull MediaCodec.BufferInfo bufferInfo) {
|
||||
getAacData.getAacData(byteBuffer, bufferInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
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);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void setSampleRate(int sampleRate) {
|
||||
this.sampleRate = sampleRate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void formatChanged(@NonNull MediaCodec mediaCodec, @NonNull MediaFormat mediaFormat) {
|
||||
getAacData.onAudioFormat(mediaFormat);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package com.pedro.encoder.audio;
|
||||
|
||||
import android.media.MediaCodec;
|
||||
|
||||
import android.media.MediaFormat;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Created by pedro on 19/01/17.
|
||||
*/
|
||||
|
||||
public interface GetAacData {
|
||||
|
||||
void getAacData(ByteBuffer aacBuffer, MediaCodec.BufferInfo info);
|
||||
|
||||
void onAudioFormat(MediaFormat mediaFormat);
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
package com.pedro.encoder.input.audio;
|
||||
|
||||
import android.media.audiofx.AcousticEchoCanceler;
|
||||
import android.media.audiofx.AutomaticGainControl;
|
||||
import android.media.audiofx.NoiseSuppressor;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Created by pedro on 11/05/17.
|
||||
*/
|
||||
|
||||
public class AudioPostProcessEffect {
|
||||
|
||||
private final String TAG = "AudioPostProcessEffect";
|
||||
|
||||
private int microphoneId;
|
||||
private AcousticEchoCanceler acousticEchoCanceler = null;
|
||||
private AutomaticGainControl automaticGainControl = null;
|
||||
private NoiseSuppressor noiseSuppressor = null;
|
||||
|
||||
public AudioPostProcessEffect(int microphoneId) {
|
||||
this.microphoneId = microphoneId;
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
public void releaseAutoGainControl() {
|
||||
if (automaticGainControl != null) {
|
||||
automaticGainControl.setEnabled(false);
|
||||
automaticGainControl.release();
|
||||
automaticGainControl = null;
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
public void releaseEchoCanceler() {
|
||||
if (acousticEchoCanceler != null) {
|
||||
acousticEchoCanceler.setEnabled(false);
|
||||
acousticEchoCanceler.release();
|
||||
acousticEchoCanceler = null;
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
public void releaseNoiseSuppressor() {
|
||||
if (noiseSuppressor != null) {
|
||||
noiseSuppressor.setEnabled(false);
|
||||
noiseSuppressor.release();
|
||||
noiseSuppressor = null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package com.pedro.encoder.input.audio;
|
||||
|
||||
public abstract class CustomAudioEffect {
|
||||
|
||||
/**
|
||||
* @param pcmBuffer buffer obtained directly from the microphone.
|
||||
* @return it must be of same size that pcmBuffer parameter.
|
||||
*/
|
||||
public abstract byte[] process(byte[] pcmBuffer);
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package com.pedro.encoder.input.audio;
|
||||
|
||||
import com.pedro.encoder.Frame;
|
||||
|
||||
/**
|
||||
* Created by pedro on 19/01/17.
|
||||
*/
|
||||
|
||||
public interface GetMicrophoneData {
|
||||
|
||||
void inputPCMData(Frame frame);
|
||||
}
|
|
@ -0,0 +1,235 @@
|
|||
package com.pedro.encoder.input.audio;
|
||||
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioPlaybackCaptureConfiguration;
|
||||
import android.media.AudioRecord;
|
||||
import android.media.MediaRecorder;
|
||||
import android.media.projection.MediaProjection;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.util.Log;
|
||||
import com.pedro.encoder.Frame;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Created by pedro on 19/01/17.
|
||||
*/
|
||||
|
||||
public class MicrophoneManager {
|
||||
|
||||
private final String TAG = "MicrophoneManager";
|
||||
private static final int BUFFER_SIZE = 4096;
|
||||
protected AudioRecord audioRecord;
|
||||
private GetMicrophoneData getMicrophoneData;
|
||||
private ByteBuffer pcmBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
|
||||
private 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 int channel = AudioFormat.CHANNEL_IN_STEREO;
|
||||
private boolean muted = false;
|
||||
private AudioPostProcessEffect audioPostProcessEffect;
|
||||
HandlerThread handlerThread;
|
||||
private CustomAudioEffect customAudioEffect = new NoAudioEffect();
|
||||
|
||||
public MicrophoneManager(GetMicrophoneData getMicrophoneData) {
|
||||
this.getMicrophoneData = getMicrophoneData;
|
||||
}
|
||||
|
||||
public void setCustomAudioEffect(CustomAudioEffect customAudioEffect) {
|
||||
this.customAudioEffect = customAudioEffect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create audio record
|
||||
*/
|
||||
public void createMicrophone() {
|
||||
createMicrophone(sampleRate, true, false, false);
|
||||
Log.i(TAG, "Microphone created, " + sampleRate + "hz, Stereo");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create audio record with params and default audio source
|
||||
*/
|
||||
public void createMicrophone(int sampleRate, boolean isStereo, boolean echoCanceler,
|
||||
boolean noiseSuppressor) {
|
||||
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.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Start record and get data
|
||||
*/
|
||||
public synchronized void start() {
|
||||
init();
|
||||
handlerThread = new HandlerThread(TAG);
|
||||
handlerThread.start();
|
||||
Handler handler = new Handler(handlerThread.getLooper());
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
while (running) {
|
||||
Frame frame = read();
|
||||
if (frame != null) {
|
||||
getMicrophoneData.inputPCMData(frame);
|
||||
} else {
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void init() {
|
||||
if (audioRecord != null) {
|
||||
audioRecord.startRecording();
|
||||
running = true;
|
||||
Log.i(TAG, "Microphone started");
|
||||
} else {
|
||||
Log.e(TAG, "Error starting, microphone was stopped or not created, "
|
||||
+ "use createMicrophone() before start()");
|
||||
}
|
||||
}
|
||||
|
||||
public void mute() {
|
||||
muted = true;
|
||||
}
|
||||
|
||||
public void unMute() {
|
||||
muted = false;
|
||||
}
|
||||
|
||||
public boolean isMuted() {
|
||||
return muted;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop and release microphone
|
||||
*/
|
||||
public synchronized void stop() {
|
||||
running = false;
|
||||
created = false;
|
||||
if (handlerThread != null) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
handlerThread.quitSafely();
|
||||
} else {
|
||||
handlerThread.quit();
|
||||
}
|
||||
}
|
||||
if (audioRecord != null) {
|
||||
audioRecord.setRecordPositionUpdateListener(null);
|
||||
audioRecord.stop();
|
||||
audioRecord.release();
|
||||
audioRecord = null;
|
||||
}
|
||||
if (audioPostProcessEffect != null) {
|
||||
audioPostProcessEffect.releaseEchoCanceler();
|
||||
audioPostProcessEffect.releaseNoiseSuppressor();
|
||||
}
|
||||
Log.i(TAG, "Microphone stopped");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get PCM buffer size
|
||||
*/
|
||||
private int getPcmBufferSize() {
|
||||
int pcmBufSize =
|
||||
AudioRecord.getMinBufferSize(sampleRate, channel, AudioFormat.ENCODING_PCM_16BIT);
|
||||
return pcmBufSize * 5;
|
||||
}
|
||||
|
||||
public int getMaxInputSize() {
|
||||
return BUFFER_SIZE;
|
||||
}
|
||||
|
||||
public int getSampleRate() {
|
||||
return sampleRate;
|
||||
}
|
||||
|
||||
public void setSampleRate(int sampleRate) {
|
||||
this.sampleRate = sampleRate;
|
||||
}
|
||||
|
||||
public int getAudioFormat() {
|
||||
return audioFormat;
|
||||
}
|
||||
|
||||
public int getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return running;
|
||||
}
|
||||
|
||||
public boolean isCreated() {
|
||||
return created;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
package com.pedro.encoder.input.audio;
|
||||
|
||||
import android.os.HandlerThread;
|
||||
import android.util.Log;
|
||||
|
||||
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 {
|
||||
|
||||
private final String TAG = "MicMM";
|
||||
|
||||
public MicrophoneManagerManual() {
|
||||
super(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start record and get data
|
||||
*/
|
||||
@Override
|
||||
public synchronized void start() {
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
if (audioRecord != null) {
|
||||
audioRecord.startRecording();
|
||||
running = true;
|
||||
Log.i(TAG, "Microphone started");
|
||||
} else {
|
||||
Log.e(TAG, "Error starting, microphone was stopped or not created, "
|
||||
+ "use createMicrophone() before start()");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call when you need mic samples.
|
||||
* This method will block until numBytes worth of samples are ready.
|
||||
*/
|
||||
public int read(ByteBuffer directBuffer, int numBytes) {
|
||||
directBuffer.rewind();
|
||||
// write to the buffer and return number of bytes written.
|
||||
return audioRecord.read(directBuffer, numBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop and release microphone
|
||||
*/
|
||||
public synchronized void stop() {
|
||||
// handlerThread must not be null, else the stop impl will throw
|
||||
handlerThread = new HandlerThread("nothing");
|
||||
super.stop();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package com.pedro.encoder.input.audio;
|
||||
|
||||
public class NoAudioEffect extends CustomAudioEffect {
|
||||
|
||||
@Override
|
||||
public byte[] process(byte[] pcmBuffer) {
|
||||
return pcmBuffer;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,243 @@
|
|||
package com.pedro.encoder.input.decoder;
|
||||
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaExtractor;
|
||||
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.PCMUtil;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Created by pedro on 20/06/17.
|
||||
*/
|
||||
public class AudioDecoder {
|
||||
|
||||
private final String TAG = "AudioDecoder";
|
||||
|
||||
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) {
|
||||
this.getMicrophoneData = getMicrophoneData;
|
||||
this.audioDecoderInterface = audioDecoderInterface;
|
||||
this.loopFileInterface = loopFileInterface;
|
||||
}
|
||||
|
||||
public boolean initExtractor(String filePath) throws IOException {
|
||||
decoding = false;
|
||||
audioExtractor = new MediaExtractor();
|
||||
audioExtractor.setDataSource(filePath);
|
||||
for (int i = 0; i < audioExtractor.getTrackCount() && !mime.startsWith("audio/"); i++) {
|
||||
audioFormat = audioExtractor.getTrackFormat(i);
|
||||
mime = audioFormat.getString(MediaFormat.KEY_MIME);
|
||||
if (mime.startsWith("audio/")) {
|
||||
audioExtractor.selectTrack(i);
|
||||
} else {
|
||||
audioFormat = null;
|
||||
}
|
||||
}
|
||||
if (audioFormat != null) {
|
||||
channels = audioFormat.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];
|
||||
}
|
||||
return true;
|
||||
//audio decoder not supported
|
||||
} else {
|
||||
mime = "";
|
||||
audioFormat = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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 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();
|
||||
startMs = System.currentTimeMillis();
|
||||
while (decoding) {
|
||||
int inIndex = audioDecoder.dequeueInputBuffer(10000);
|
||||
if (inIndex >= 0) {
|
||||
ByteBuffer buffer = inputBuffers[inIndex];
|
||||
int sampleSize = audioExtractor.readSampleData(buffer, 0);
|
||||
if (sampleSize < 0) {
|
||||
audioDecoder.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
|
||||
} else {
|
||||
audioDecoder.queueInputBuffer(inIndex, 0, sampleSize, audioExtractor.getSampleTime(), 0);
|
||||
audioExtractor.advance();
|
||||
}
|
||||
|
||||
int outIndex = audioDecoder.dequeueOutputBuffer(audioInfo, 10000);
|
||||
switch (outIndex) {
|
||||
case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
|
||||
outputBuffers = audioDecoder.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) {
|
||||
try {
|
||||
Thread.sleep(10);
|
||||
} catch (InterruptedException e) {
|
||||
if (thread != null) thread.interrupt();
|
||||
return;
|
||||
}
|
||||
}
|
||||
ByteBuffer outBuffer = outputBuffers[outIndex];
|
||||
//This buffer is PCM data
|
||||
if (muted) {
|
||||
outBuffer.get(pcmBufferMuted, 0,
|
||||
outBuffer.remaining() <= pcmBufferMuted.length ? 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 (channels > 2) { //downgrade to stereo
|
||||
byte[] bufferStereo = PCMUtil.pcmToStereo(pcmBuffer, channels);
|
||||
getMicrophoneData.inputPCMData(new Frame(bufferStereo, 0, bufferStereo.length));
|
||||
} else {
|
||||
getMicrophoneData.inputPCMData(new Frame(pcmBuffer, 0, pcmBuffer.length));
|
||||
}
|
||||
}
|
||||
audioDecoder.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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public double getTime() {
|
||||
if (decoding) {
|
||||
return audioExtractor.getSampleTime() / 10E5;
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
|
||||
public void unMute() {
|
||||
muted = false;
|
||||
}
|
||||
|
||||
public boolean isMuted() {
|
||||
return muted;
|
||||
}
|
||||
|
||||
public int getSampleRate() {
|
||||
return sampleRate;
|
||||
}
|
||||
|
||||
public boolean isStereo() {
|
||||
return isStereo;
|
||||
}
|
||||
|
||||
public double getDuration() {
|
||||
return duration / 10E5;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package com.pedro.encoder.input.decoder;
|
||||
|
||||
/**
|
||||
* Created by pedro on 6/07/17.
|
||||
*/
|
||||
|
||||
public interface AudioDecoderInterface {
|
||||
|
||||
void onAudioDecoderFinished();
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package com.pedro.encoder.input.decoder;
|
||||
|
||||
/**
|
||||
* Created by pedro on 4/03/18.
|
||||
*/
|
||||
|
||||
public interface LoopFileInterface {
|
||||
|
||||
void onReset(boolean isVideo);
|
||||
}
|
|
@ -0,0 +1,187 @@
|
|||
package com.pedro.encoder.input.decoder;
|
||||
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaExtractor;
|
||||
import android.media.MediaFormat;
|
||||
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";
|
||||
|
||||
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) {
|
||||
this.videoDecoderInterface = videoDecoderInterface;
|
||||
this.loopFileInterface = loopFileInterface;
|
||||
}
|
||||
|
||||
public boolean initExtractor(String filePath) throws IOException {
|
||||
decoding = false;
|
||||
videoExtractor = new MediaExtractor();
|
||||
videoExtractor.setDataSource(filePath);
|
||||
for (int i = 0; i < videoExtractor.getTrackCount() && !mime.startsWith("video/"); i++) {
|
||||
videoFormat = videoExtractor.getTrackFormat(i);
|
||||
mime = videoFormat.getString(MediaFormat.KEY_MIME);
|
||||
if (mime.startsWith("video/")) {
|
||||
videoExtractor.selectTrack(i);
|
||||
} else {
|
||||
videoFormat = null;
|
||||
}
|
||||
}
|
||||
if (videoFormat != null) {
|
||||
width = videoFormat.getInteger(MediaFormat.KEY_WIDTH);
|
||||
height = videoFormat.getInteger(MediaFormat.KEY_HEIGHT);
|
||||
duration = videoFormat.getLong(MediaFormat.KEY_DURATION);
|
||||
return true;
|
||||
//video decoder not supported
|
||||
} else {
|
||||
mime = "";
|
||||
videoFormat = 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;
|
||||
}
|
||||
}
|
||||
|
||||
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 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();
|
||||
startMs = System.currentTimeMillis();
|
||||
while (decoding) {
|
||||
int inIndex = videoDecoder.dequeueInputBuffer(10000);
|
||||
if (inIndex >= 0) {
|
||||
ByteBuffer buffer = inputBuffers[inIndex];
|
||||
int sampleSize = videoExtractor.readSampleData(buffer, 0);
|
||||
if (sampleSize < 0) {
|
||||
videoDecoder.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
|
||||
} else {
|
||||
videoDecoder.queueInputBuffer(inIndex, 0, sampleSize, videoExtractor.getSampleTime(), 0);
|
||||
videoExtractor.advance();
|
||||
}
|
||||
}
|
||||
int outIndex = videoDecoder.dequeueOutputBuffer(videoInfo, 10000);
|
||||
if (outIndex >= 0) {
|
||||
while (videoExtractor.getSampleTime() / 1000
|
||||
> System.currentTimeMillis() - startMs + seekTime) {
|
||||
try {
|
||||
Thread.sleep(10);
|
||||
} catch (InterruptedException e) {
|
||||
if (thread != null) thread.interrupt();
|
||||
return;
|
||||
}
|
||||
}
|
||||
videoDecoder.releaseOutputBuffer(outIndex, videoInfo.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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public double getTime() {
|
||||
if (decoding) {
|
||||
return videoExtractor.getSampleTime() / 10E5;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public double getDuration() {
|
||||
return duration / 10E5;
|
||||
}
|
||||
}
|