Compare commits

...

6 Commits

Author SHA1 Message Date
pie
1287bbdb53 Fixes default values and adds explanation message for permission request
Some checks failed
Build / build (push) Has been cancelled
2026-05-14 14:27:40 +02:00
pie
707e1295c5 Fix extra signing block problem on Fdroid
Some checks failed
Build / build (push) Has been cancelled
2026-04-29 23:07:37 +02:00
pie
ca26daa3ee Added minimal metadata
Some checks failed
Build / build (push) Has been cancelled
2026-04-29 16:39:07 +02:00
pie
fd712c54b8 Renamed main project
Some checks failed
Build / build (push) Has been cancelled
2026-04-29 15:11:23 +02:00
pie
2ed0cb1a52 Changed app id
Some checks failed
Build / build (push) Has been cancelled
2026-04-28 15:34:49 +02:00
pie
1f136f9aea Terrible settings to make the amazing streampack boilerplate code usable.
Some checks failed
Build / build (push) Has been cancelled
2026-04-15 21:12:26 +02:00
57 changed files with 254 additions and 230 deletions

View File

@@ -11,8 +11,15 @@ android {
version = release(36) version = release(36)
} }
dependenciesInfo {
// Disables dependency metadata when building APKs.
includeInApk = false
// Disables dependency metadata when building Android App Bundles.
includeInBundle = false
}
defaultConfig { defaultConfig {
applicationId = "io.github.thibaultbee.streampack.app" applicationId = "org.esiliati.repo.picostreaming.app"
minSdk = 24 minSdk = 24
targetSdk = 36 targetSdk = 36
versionCode = 1 versionCode = 1
@@ -58,8 +65,9 @@ dependencies {
implementation(libs.material) implementation(libs.material)
implementation(libs.constraintlayout) implementation(libs.constraintlayout)
implementation(libs.lifecycle.runtime.ktx) implementation(libs.lifecycle.runtime.ktx)
implementation(libs.preference)
testImplementation(libs.junit) testImplementation(libs.junit)
androidTestImplementation(libs.ext.junit) androidTestImplementation(libs.ext.junit)
androidTestImplementation(libs.espresso.core) androidTestImplementation(libs.espresso.core)
} }

View File

@@ -1,19 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<!-- For play store --> <!-- For play store -->
<uses-feature <uses-feature
android:name="android.hardware.camera" android:name="android.hardware.camera"
android:required="true" /> android:required="true" />
<uses-feature <uses-feature
android:name="android.hardware.camera.autofocus" android:name="android.hardware.camera.autofocus"
android:required="false" /> android:required="false" /> <!-- Required permissions -->
<!-- Required permissions -->
<uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application <application
android:allowBackup="true" android:allowBackup="true"
@@ -21,12 +19,13 @@
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.MyStreamingApp" android:theme="@style/Theme.PicoStreamingApp"
tools:targetApi="31"> tools:targetApi="31">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:theme="@style/Theme.AppCompat.NoActionBar"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@@ -38,6 +37,15 @@
android:name="android.app.lib_name" android:name="android.app.lib_name"
android:value="" /> android:value="" />
</activity> </activity>
<activity
android:name=".SettingsActivity"
android:label="@string/title_activity_settings"
android:parentActivityName=".MainActivity" >
<!-- Parent activity meta-data to support 4.0 and lower -->
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".MainActivity" />
</activity>
</application> </application>
</manifest> </manifest>

View File

@@ -2,19 +2,23 @@ package io.github.thibaultbee.streampack.app
import android.Manifest import android.Manifest
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent
import android.content.pm.ActivityInfo import android.content.pm.ActivityInfo
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.WindowManager.LayoutParams
import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.annotation.RequiresPermission import androidx.annotation.RequiresPermission
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import io.github.thibaultbee.streampack.app.databinding.ActivityMainBinding import io.github.thibaultbee.streampack.app.databinding.ActivityMainBinding
import io.github.thibaultbee.streampack.core.elements.sources.video.camera.extensions.defaultCameraId
import io.github.thibaultbee.streampack.core.streamers.lifecycle.StreamerActivityLifeCycleObserver
import io.github.thibaultbee.streampack.app.utils.PermissionsManager import io.github.thibaultbee.streampack.app.utils.PermissionsManager
import io.github.thibaultbee.streampack.app.utils.showDialog import io.github.thibaultbee.streampack.app.utils.showDialog
import io.github.thibaultbee.streampack.app.utils.toast import io.github.thibaultbee.streampack.app.utils.toast
import io.github.thibaultbee.streampack.core.elements.sources.video.camera.extensions.defaultCameraId
import io.github.thibaultbee.streampack.core.streamers.lifecycle.StreamerActivityLifeCycleObserver
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
@@ -41,7 +45,7 @@ class MainActivity : AppCompatActivity() {
// Explain why we need permissions // Explain why we need permissions
showDialog( showDialog(
title = "Permissions denied", title = "Permissions denied",
message = "Explain why you need to grant $permissions permissions to stream", message = getString(R.string.message),
positiveButtonText = R.string.accept, positiveButtonText = R.string.accept,
onPositiveButtonClick = { onRequiredPermissionLastTime() }, onPositiveButtonClick = { onRequiredPermissionLastTime() },
negativeButtonText = R.string.denied negativeButtonText = R.string.denied
@@ -70,6 +74,24 @@ class MainActivity : AppCompatActivity() {
} }
private fun bindProperties() { private fun bindProperties() {
val getContent = registerForActivityResult(ActivityResultContracts.StartActivityForResult())
{
val streamResolution = SettingsActivity.getResolution(applicationContext)
val (w, h) = streamResolution.split("|").map { it.toInt() }.let { it[0] to it[1] }
val streamBitrate = SettingsActivity.getBitrate(applicationContext)
lifecycleScope.launch {
viewModel.setVideoConfig(w, h, streamBitrate.toInt())
}
}
binding.settingsButton.setOnClickListener { view ->
val intent: Intent = Intent(this, SettingsActivity::class.java)
getContent.launch(intent)
}
binding.liveButton.setOnCheckedChangeListener { view, isChecked -> binding.liveButton.setOnCheckedChangeListener { view, isChecked ->
if (view.isPressed) { if (view.isPressed) {
if (isChecked) { if (isChecked) {
@@ -77,9 +99,13 @@ class MainActivity : AppCompatActivity() {
* Dispatch from main thread is forced to avoid making network call on main thread * Dispatch from main thread is forced to avoid making network call on main thread
* with coroutines. * with coroutines.
*/ */
// lifecycleScope.launch {
val streamUrl = SettingsActivity.getServer(applicationContext)
// }
lifecycleScope.launch { lifecycleScope.launch {
try { try {
viewModel.startStream() window.addFlags(LayoutParams.FLAG_KEEP_SCREEN_ON)
viewModel.startStream(streamUrl)
} catch (e: Exception) { } catch (e: Exception) {
binding.liveButton.isChecked = false binding.liveButton.isChecked = false
Log.e(TAG, "Failed to connect", e) Log.e(TAG, "Failed to connect", e)
@@ -88,6 +114,7 @@ class MainActivity : AppCompatActivity() {
} }
} else { } else {
lifecycleScope.launch { lifecycleScope.launch {
window.clearFlags(LayoutParams.FLAG_KEEP_SCREEN_ON)
viewModel.stopStream() viewModel.stopStream()
} }
} }
@@ -197,4 +224,4 @@ class MainActivity : AppCompatActivity() {
companion object { companion object {
private const val TAG = "MainActivity" private const val TAG = "MainActivity"
} }
} }

View File

@@ -1,31 +1,42 @@
package io.github.thibaultbee.streampack.app package io.github.thibaultbee.streampack.app
import android.app.AlertDialog
import android.Manifest import android.Manifest
import android.content.DialogInterface
import android.media.AudioFormat import android.media.AudioFormat
import android.media.MediaFormat import android.media.MediaFormat
import android.text.InputType
import android.util.Log
import android.util.Size import android.util.Size
import android.widget.EditText
import androidx.annotation.RequiresPermission import androidx.annotation.RequiresPermission
import androidx.core.content.ContentProviderCompat.requireContext
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.asLiveData import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import io.github.thibaultbee.streampack.app.data.rotation.RotationRepository import io.github.thibaultbee.streampack.app.data.rotation.RotationRepository
import io.github.thibaultbee.streampack.core.configuration.mediadescriptor.MediaDescriptor
import io.github.thibaultbee.streampack.core.elements.sources.audio.audiorecord.MicrophoneSourceFactory import io.github.thibaultbee.streampack.core.elements.sources.audio.audiorecord.MicrophoneSourceFactory
import io.github.thibaultbee.streampack.core.elements.sources.video.camera.ICameraSource
import io.github.thibaultbee.streampack.core.interfaces.setCameraId import io.github.thibaultbee.streampack.core.interfaces.setCameraId
import io.github.thibaultbee.streampack.core.interfaces.startStream import io.github.thibaultbee.streampack.core.interfaces.startStream
import io.github.thibaultbee.streampack.core.pipelines.inputs.IVideoInput
import io.github.thibaultbee.streampack.core.streamers.single.AudioConfig import io.github.thibaultbee.streampack.core.streamers.single.AudioConfig
import io.github.thibaultbee.streampack.core.streamers.single.SingleStreamer import io.github.thibaultbee.streampack.core.streamers.single.SingleStreamer
import io.github.thibaultbee.streampack.core.streamers.single.VideoConfig import io.github.thibaultbee.streampack.core.streamers.single.VideoConfig
import io.github.thibaultbee.streampack.core.utils.extensions.isClosedException import io.github.thibaultbee.streampack.core.utils.extensions.isClosedException
import io.github.thibaultbee.streampack.ext.rtmp.configuration.mediadescriptor.RtmpMediaDescriptor
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class MainViewModel( class MainViewModel(
private val rotationRepository: RotationRepository, private val rotationRepository: RotationRepository,
val streamer: SingleStreamer val streamer: SingleStreamer,
) : ViewModel() { ) : ViewModel() {
private val defaultDispatcher = Dispatchers.Default private val defaultDispatcher = Dispatchers.Default
@@ -70,13 +81,13 @@ class MainViewModel(
* *
* Replace with a valid URL. * Replace with a valid URL.
*/ */
suspend fun startStream() { suspend fun startStream(streamUrl: String) {
_isTryingConnectionLiveData.postValue(true) _isTryingConnectionLiveData.postValue(true)
try { try {
/** /**
* For SRT, use srt://my.server.url:9998?streamid=myStreamId&passphrase=myPassphrase * For SRT, use srt://my.server.url:9998?streamid=myStreamId&passphrase=myPassphrase
*/ */
streamer.startStream("rtmp://my.server.url:1935/app/streamKey") streamer.startStream(streamUrl)
} finally { } finally {
_isTryingConnectionLiveData.postValue(false) _isTryingConnectionLiveData.postValue(false)
} }
@@ -133,6 +144,27 @@ class MainViewModel(
streamer.setVideoConfig(videoConfig) streamer.setVideoConfig(videoConfig)
} }
/**
* Sets the video configuration.
*
* You can verify the device supported configuration with [SingleStreamer.getInfo].
*/
suspend fun setVideoConfig(w: Int, h: Int,fps: Int) {
/**
* There are other parameters in the [VideoConfig] such as:
* - bitrate
* - profile
* - level
* - gopSize
* They will be initialized with an appropriate default value.
*/
val videoConfig = VideoConfig(
mimeType = MediaFormat.MIMETYPE_VIDEO_AVC, resolution = Size(w, h), fps = fps
)
streamer.setVideoConfig(videoConfig)
}
/** /**
* Sets the microphone as the audio source. * Sets the microphone as the audio source.
*/ */

View File

@@ -0,0 +1,42 @@
package io.github.thibaultbee.streampack.app
import android.content.Context
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.EditTextPreference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
class SettingsActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//PreferenceManager.setDefaultValues(this, R.xml.root_preferences, false);
setContentView(R.layout.settings_activity)
if (savedInstanceState == null) {
supportFragmentManager
.beginTransaction()
.replace(R.id.settings, SettingsFragment())
.commit()
}
supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
class SettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.root_preferences, rootKey)
}
}
companion object {
fun getServer(context: Context): String =
PreferenceManager.getDefaultSharedPreferences(context).getString("pref_server", "") ?: ""
fun getResolution(context: Context): String =
PreferenceManager.getDefaultSharedPreferences(context).getString("pref_resolution", "") ?: ""
fun getBitrate(context: Context): String =
PreferenceManager.getDefaultSharedPreferences(context).getString("pref_bitrate", "") ?: ""
}
}

View File

@@ -1,30 +0,0 @@
<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>

View File

@@ -1,170 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#ffffff"
android:pathData="M3,18h18v-2L3,16v2zM3,13h18v-2L3,11v2zM3,6v2h18L21,6L3,6z" />
</vector>

View File

@@ -15,6 +15,21 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<ImageButton
android:id="@+id/settingsButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:layout_marginEnd="32dp"
android:background="@drawable/ic_menu"
android:contentDescription="Settings"
android:textColor="@color/white"
android:textOff=""
android:textOn=""
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
/>
<ToggleButton <ToggleButton
android:id="@+id/liveButton" android:id="@+id/liveButton"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@@ -0,0 +1,9 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/settings"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" /> <background android:drawable="@mipmap/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground" /> <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@mipmap/ic_launcher_monochrome"/>
</adaptive-icon> </adaptive-icon>

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 870 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

View File

@@ -1,6 +1,6 @@
<resources xmlns:tools="http://schemas.android.com/tools"> <resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. --> <!-- Base application theme. -->
<style name="Theme.MyStreamingApp" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> <style name="Theme.PicoStreamingApp" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. --> <!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item> <item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item> <item name="colorPrimaryVariant">@color/purple_700</item>
@@ -13,4 +13,4 @@
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item> <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. --> <!-- Customize your theme here. -->
</style> </style>
</resources> </resources>

View File

@@ -0,0 +1,28 @@
<resources>
<string-array name="resolution_entries">
<item>480px360p</item>
<item>720px480p</item>
<item>1080px720p</item>
</string-array>
<string-array name="resolution_values">
<item>480|360</item>
<item>720|480</item>
<item>1080|720</item>
</string-array>
<array name="default_resolution">
<item>1080|720</item>
</array>
<string-array name="bitrate_entries">
<item>15 fps</item>
<item>25 fps</item>
<item>50 fps</item>
</string-array>
<string-array name="bitrate_values">
<item>15</item>
<item>25</item>
<item>50</item>
</string-array>
<array name="default_bitrate">
<item>25</item>
</array>
</resources>

View File

@@ -1,5 +1,12 @@
<resources> <resources>
<string name="app_name">My Streaming App</string> <string name="app_name">Pico Streaming App</string>
<string name="denied">Denied</string> <string name="denied">Denied</string>
<string name="accept">Accept</string> <string name="accept">Accept</string>
<string name="message">Video and Audio permissions are required to stream from main camera and microphone.</string>
<string name="title_activity_settings">Settings</string>
<!-- Preference Titles -->
<string name="messages_header">Connection</string>
<string name="sync_header">Sync</string>
</resources> </resources>

View File

@@ -1,6 +1,6 @@
<resources xmlns:tools="http://schemas.android.com/tools"> <resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. --> <!-- Base application theme. -->
<style name="Theme.MyStreamingApp" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> <style name="Theme.PicoStreamingApp" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. --> <!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item> <item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item> <item name="colorPrimaryVariant">@color/purple_700</item>
@@ -13,4 +13,4 @@
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item> <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. --> <!-- Customize your theme here. -->
</style> </style>
</resources> </resources>

View File

@@ -0,0 +1,33 @@
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory app:title="@string/messages_header">
<EditTextPreference
app:key="pref_server"
app:title="Stream URL"
app:summary="srt://host:port or rtsp://..."
app:useSimpleSummaryProvider="true"
app:defaultValue=""/>
</PreferenceCategory>
<PreferenceCategory app:title="Video">
<ListPreference
app:key="pref_resolution"
app:title="Resolution"
app:summary="Set video acquisition resolution"
app:useSimpleSummaryProvider="true"
app:entries="@array/resolution_entries"
app:entryValues="@array/resolution_values"
app:defaultValue="@array/default_resolution" />
<ListPreference
app:key="pref_bitrate"
app:title="Bitrate"
app:summary="Video bitrate"
app:useSimpleSummaryProvider="true"
app:entries="@array/bitrate_entries"
app:entryValues="@array/bitrate_values"
app:defaultValue="@array/default_bitrate"/>
</PreferenceCategory>
</PreferenceScreen>

View File

@@ -0,0 +1,5 @@
Minimal RTMP streaming app heavily based on StreamPack-boilerplate.
It really just adds a bunch of options to make it flexible enough to stream from an Android phone.
Sends video to specified RTMP/RTSP url using main camera and microphone.

View File

@@ -0,0 +1 @@
Minimal RTMP streaming app.

View File

@@ -0,0 +1 @@
PicoStreamingApp

View File

@@ -10,6 +10,7 @@ kotlin = "2.3.0"
lifecycleRuntimeKtx = "2.10.0" lifecycleRuntimeKtx = "2.10.0"
material = "1.13.0" material = "1.13.0"
streampack = "3.1.1" streampack = "3.1.1"
preference = "1.2.0"
[libraries] [libraries]
appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
@@ -24,6 +25,7 @@ streampack-core = { group = "io.github.thibaultbee.streampack", name = "streampa
streampack-ui = { group = "io.github.thibaultbee.streampack", name = "streampack-ui", version.ref = "streampack" } streampack-ui = { group = "io.github.thibaultbee.streampack", name = "streampack-ui", version.ref = "streampack" }
streampack-rtmp = { group = "io.github.thibaultbee.streampack", name = "streampack-rtmp", version.ref = "streampack" } streampack-rtmp = { group = "io.github.thibaultbee.streampack", name = "streampack-rtmp", version.ref = "streampack" }
streampack-srt = { group = "io.github.thibaultbee.streampack", name = "streampack-srt", version.ref = "streampack" } streampack-srt = { group = "io.github.thibaultbee.streampack", name = "streampack-srt", version.ref = "streampack" }
preference = { group = "androidx.preference", name = "preference", version.ref = "preference" }
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }

View File

@@ -19,5 +19,5 @@ dependencyResolutionManagement {
} }
} }
rootProject.name = "MyStreamingApp" rootProject.name = "PicoStreamingApp"
include(":app") include(":app")