Merge pull request 'manage-stream-resolution' (#10) from manage-stream-resolution into master

Reviewed-on: https://codeberg.org/Schoumi/PeerTubeLive/pulls/10
This commit is contained in:
Schoumi 2021-07-27 15:53:33 +02:00
commit 6031e37959
12 changed files with 138 additions and 58 deletions

View File

@ -10,7 +10,7 @@ android {
defaultConfig { defaultConfig {
applicationId "fr.mobdev.peertubelive" applicationId "fr.mobdev.peertubelive"
minSdkVersion 19 minSdkVersion 21
targetSdkVersion 30 targetSdkVersion 30
versionCode 1 versionCode 1
versionName "1.0" versionName "1.0"

View File

@ -58,6 +58,8 @@ class CreateLiveActivity : AppCompatActivity() {
binding.privacyList.visibility = View.GONE binding.privacyList.visibility = View.GONE
binding.saveReplayLayout.visibility = View.GONE binding.saveReplayLayout.visibility = View.GONE
binding.saveReplayInfo.visibility = View.GONE binding.saveReplayInfo.visibility = View.GONE
binding.resolution.visibility = View.GONE
binding.resolutionList.visibility = View.GONE
startLive = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { startLive = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
setResult(it.resultCode) setResult(it.resultCode)
@ -93,7 +95,9 @@ class CreateLiveActivity : AppCompatActivity() {
val nsfw = binding.nsfw.isChecked val nsfw = binding.nsfw.isChecked
val replay = binding.saveReplay.isChecked val replay = binding.saveReplay.isChecked
val streamSettings = StreamSettings(title,channel,privacy,category,language,licence,description,comments,download,nsfw,replay) val resolution = StreamData.STREAM_RESOLUTION.values()[binding.resolutionList.selectedItemPosition]
val streamSettings = StreamSettings(title,channel,privacy,category,language,licence,description,comments,download,nsfw,replay,resolution)
DatabaseManager.updateStreamSettings(this,streamSettings) DatabaseManager.updateStreamSettings(this,streamSettings)
if(title.isEmpty()) if(title.isEmpty())
{ {
@ -262,6 +266,8 @@ class CreateLiveActivity : AppCompatActivity() {
binding.privacyList.visibility = View.VISIBLE binding.privacyList.visibility = View.VISIBLE
binding.saveReplayLayout.visibility = View.VISIBLE binding.saveReplayLayout.visibility = View.VISIBLE
binding.saveReplayInfo.visibility = View.VISIBLE binding.saveReplayInfo.visibility = View.VISIBLE
binding.resolution.visibility = View.VISIBLE
binding.resolutionList.visibility = View.VISIBLE
restoreSettings() restoreSettings()
} }
} }
@ -284,8 +290,9 @@ class CreateLiveActivity : AppCompatActivity() {
InstanceManager.createLive(this,oAuthData.baseUrl!!,oAuthData,streamSettings,object : InstanceManager.InstanceListener { InstanceManager.createLive(this,oAuthData.baseUrl!!,oAuthData,streamSettings,object : InstanceManager.InstanceListener {
override fun onSuccess(args: Bundle?) { override fun onSuccess(args: Bundle?) {
if(args != null) { if(args != null) {
val streamData = args.getParcelable<StreamData>(InstanceManager.EXTRA_DATA)!! val urlKey = args.getParcelable<StreamData>(InstanceManager.EXTRA_DATA)!!
val intent = Intent(this@CreateLiveActivity, StreamActivity::class.java) val intent = Intent(this@CreateLiveActivity, StreamActivity::class.java)
val streamData = StreamData(urlKey.url,urlKey.key,streamSettings.resolution)
intent.putExtra(InstanceManager.EXTRA_DATA,streamData) intent.putExtra(InstanceManager.EXTRA_DATA,streamData)
dialog.dismiss() dialog.dismiss()
startLive.launch(intent) startLive.launch(intent)
@ -381,6 +388,8 @@ class CreateLiveActivity : AppCompatActivity() {
} }
} }
} }
binding.resolutionList.setSelection(settings.resolution.ordinal)
} }
} }

View File

@ -50,6 +50,9 @@ class MainActivity : AppCompatActivity() {
StreamActivity.STOP -> { StreamActivity.STOP -> {
alertBuilder.setMessage(R.string.stop_reason) alertBuilder.setMessage(R.string.stop_reason)
} }
StreamActivity.NETWORK_ERROR -> {
alertBuilder.setMessage(R.string.network_reason)
}
} }
alertBuilder.setPositiveButton(android.R.string.ok,null) alertBuilder.setPositiveButton(android.R.string.ok,null)
alertBuilder.show() alertBuilder.show()

View File

@ -19,7 +19,7 @@ import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.databinding.DataBindingUtil import androidx.databinding.DataBindingUtil
import com.pedro.encoder.input.video.CameraHelper import com.pedro.encoder.input.video.CameraHelper
import com.pedro.rtplibrary.rtmp.RtmpCamera1 import com.pedro.rtplibrary.rtmp.RtmpCamera2
import net.ossrs.rtmp.ConnectCheckerRtmp import net.ossrs.rtmp.ConnectCheckerRtmp
import fr.mobdev.peertubelive.R import fr.mobdev.peertubelive.R
import fr.mobdev.peertubelive.databinding.StreamBinding import fr.mobdev.peertubelive.databinding.StreamBinding
@ -30,7 +30,7 @@ class StreamActivity : AppCompatActivity() {
private lateinit var binding: StreamBinding private lateinit var binding: StreamBinding
private lateinit var rtmpCamera1 : RtmpCamera1 private lateinit var rtmpCamera2 : RtmpCamera2
private lateinit var streamData: StreamData private lateinit var streamData: StreamData
private lateinit var orientationEventListener: OrientationEventListener private lateinit var orientationEventListener: OrientationEventListener
private lateinit var lockReceiver: BroadcastReceiver private lateinit var lockReceiver: BroadcastReceiver
@ -47,6 +47,7 @@ class StreamActivity : AppCompatActivity() {
const val LOCK :Int = 2 const val LOCK :Int = 2
const val BACK :Int = 3 const val BACK :Int = 3
const val STOP :Int = 4 const val STOP :Int = 4
const val NETWORK_ERROR :Int = 5
} }
@ -84,7 +85,7 @@ class StreamActivity : AppCompatActivity() {
if (lastScreenOrientation != screenOrientation && orientationCounter > 30) { if (lastScreenOrientation != screenOrientation && orientationCounter > 30) {
screenOrientation = lastScreenOrientation screenOrientation = lastScreenOrientation
rtmpCamera1.glInterface.setStreamRotation(screenOrientation) rtmpCamera2.glInterface.setStreamRotation(screenOrientation)
if (screenOrientation == 90) { if (screenOrientation == 90) {
localOrientation = 270 localOrientation = 270
@ -104,24 +105,24 @@ class StreamActivity : AppCompatActivity() {
binding.stop.setOnClickListener { binding.stop.setOnClickListener {
askStopLive(STOP) askStopLive(STOP)
} }
binding.switchCamera.setOnClickListener { rtmpCamera1.switchCamera() } binding.switchCamera.setOnClickListener { rtmpCamera2.switchCamera() }
binding.muteMicro.setOnClickListener { binding.muteMicro.setOnClickListener {
if (rtmpCamera1.isAudioMuted) { if (rtmpCamera2.isAudioMuted) {
rtmpCamera1.enableAudio() rtmpCamera2.enableAudio()
binding.muteMicro.setImageResource(R.drawable.baseline_volume_up_24) binding.muteMicro.setImageResource(R.drawable.baseline_volume_up_24)
} }
else { else {
rtmpCamera1.disableAudio() rtmpCamera2.disableAudio()
binding.muteMicro.setImageResource(R.drawable.baseline_volume_off_24) binding.muteMicro.setImageResource(R.drawable.baseline_volume_off_24)
} }
} }
binding.flash.setOnClickListener { binding.flash.setOnClickListener {
if (rtmpCamera1.isLanternEnabled) { if (rtmpCamera2.isLanternEnabled) {
rtmpCamera1.disableLantern() rtmpCamera2.disableLantern()
binding.flash.setImageResource(R.drawable.baseline_flash_off_24) binding.flash.setImageResource(R.drawable.baseline_flash_off_24)
} }
else { else {
rtmpCamera1.enableLantern() rtmpCamera2.enableLantern()
binding.flash.setImageResource(R.drawable.baseline_flash_on_24) binding.flash.setImageResource(R.drawable.baseline_flash_on_24)
} }
} }
@ -148,11 +149,11 @@ class StreamActivity : AppCompatActivity() {
} }
override fun surfaceDestroyed(p0: SurfaceHolder) { override fun surfaceDestroyed(p0: SurfaceHolder) {
if (this@StreamActivity::rtmpCamera1.isInitialized) { if (this@StreamActivity::rtmpCamera2.isInitialized) {
if (rtmpCamera1.isStreaming) { if (rtmpCamera2.isStreaming) {
rtmpCamera1.stopStream() rtmpCamera2.stopStream()
} }
rtmpCamera1.stopPreview() rtmpCamera2.stopPreview()
} }
} }
@ -257,7 +258,9 @@ class StreamActivity : AppCompatActivity() {
override fun onConnectionFailedRtmp(reason: String) { override fun onConnectionFailedRtmp(reason: String) {
runOnUiThread { runOnUiThread {
Toast.makeText(binding.root.context, "Connection failed", Toast.LENGTH_SHORT).show(); Toast.makeText(binding.root.context, "Connection failed", Toast.LENGTH_SHORT).show();
rtmpCamera1.stopStream() rtmpCamera2.stopStream()
setResult(NETWORK_ERROR)
finish()
} }
} }
@ -286,36 +289,48 @@ class StreamActivity : AppCompatActivity() {
} }
} }
rtmpCamera1 = RtmpCamera1(binding.surfaceView, connectChecker) rtmpCamera2 = RtmpCamera2(binding.surfaceView, connectChecker)
var resolutions = rtmpCamera1.resolutionsBack var width = 1920
var width: Int var height = 1080
var height: Int when (streamData.resolution) {
if(resolutions[0].width > resolutions[0].height) { StreamData.STREAM_RESOLUTION.p2160 -> {
width = resolutions[0].width width = 3840
height = resolutions[0].height height = 2160
} else {
width = resolutions[0].height
height = resolutions[0].width
} }
for(res in resolutions) { StreamData.STREAM_RESOLUTION.p1440 -> {
if (width * height < res.width * res.width) { width = 2560
if(res.width > res.height) { height = 1440
width = res.width
height = res.height
} else {
width = res.height
height = res.width
} }
StreamData.STREAM_RESOLUTION.p1080 -> {
width = 1920
height = 1080
}
StreamData.STREAM_RESOLUTION.p720 -> {
width = 1280
height = 720
}
StreamData.STREAM_RESOLUTION.p480 -> {
width = 640
height = 480
}
StreamData.STREAM_RESOLUTION.p360 -> {
width = 480
height = 360
} }
} }
rtmpCamera1.startPreview(width,height)
rtmpCamera2.startPreview(CameraHelper.Facing.BACK, width,height)
//start stream //start stream
if (rtmpCamera1.prepareAudio() && rtmpCamera1.prepareVideo(width,height,30,3000*1024,false,CameraHelper.getCameraOrientation(this))) { if (rtmpCamera2.prepareAudio() && rtmpCamera2.prepareVideo(width,height,30, (width*height*30*0.076).toInt(),false,CameraHelper.getCameraOrientation(this))) {
rtmpCamera1.startStream(streamData.url+"/"+streamData.key) rtmpCamera2.startStream(streamData.url+"/"+streamData.key)
} else { } else {
println("error")
/**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) */ /**This device cant init encoders, this could be for 2 reasons: The encoder selected doesnt support any configuration setted or your device hasnt a H264 or AAC encoder (in this case you can see log error valid encoder not found) */
} }
} }

View File

@ -5,6 +5,7 @@ import android.content.Context
import android.database.Cursor import android.database.Cursor
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper import android.database.sqlite.SQLiteOpenHelper
import fr.mobdev.peertubelive.objects.StreamData
class DatabaseHelper(context: Context) : SQLiteOpenHelper(context, DB_NAME, null, 2) { class DatabaseHelper(context: Context) : SQLiteOpenHelper(context, DB_NAME, null, 2) {
@ -30,6 +31,7 @@ class DatabaseHelper(context: Context) : SQLiteOpenHelper(context, DB_NAME, null
const val SETS_DOWNLOAD : String = "Download" const val SETS_DOWNLOAD : String = "Download"
const val SETS_REPLAY : String = "Replay" const val SETS_REPLAY : String = "Replay"
const val SETS_NSFW : String = "Nsfw" const val SETS_NSFW : String = "Nsfw"
const val SETS_RESOLUTION: String = "Resolution"
} }
override fun onCreate(db: SQLiteDatabase?) { override fun onCreate(db: SQLiteDatabase?) {
@ -39,7 +41,7 @@ class DatabaseHelper(context: Context) : SQLiteOpenHelper(context, DB_NAME, null
db?.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_STREAM_SETTINGS (id INTEGER PRIMARY KEY, $SETS_TITLE TEXT, $SETS_CATEGORY INTEGER, $SETS_PRIVACY 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_LANGUAGE TEXT, $SETS_LICENCE INTEGER, $SETS_COMMENTS INTEGER, " +
"$SETS_DOWNLOAD INTEGER, $SETS_REPLAY INTEGER, $SETS_NSFW INTEGER);") "$SETS_DOWNLOAD INTEGER, $SETS_REPLAY INTEGER, $SETS_NSFW INTEGER, $SETS_RESOLUTION INTEGER);")
val values = ContentValues() val values = ContentValues()
values.put("id",1) values.put("id",1)
@ -48,6 +50,7 @@ class DatabaseHelper(context: Context) : SQLiteOpenHelper(context, DB_NAME, null
values.put(SETS_DOWNLOAD,true) values.put(SETS_DOWNLOAD,true)
values.put(SETS_NSFW,false) values.put(SETS_NSFW,false)
values.put(SETS_REPLAY,false) values.put(SETS_REPLAY,false)
values.put(SETS_RESOLUTION,StreamData.STREAM_RESOLUTION.p1080.ordinal)
db?.insert(TABLE_STREAM_SETTINGS,null,values) db?.insert(TABLE_STREAM_SETTINGS,null,values)
} }

View File

@ -4,6 +4,7 @@ import android.content.ContentValues
import android.content.Context import android.content.Context
import android.database.Cursor import android.database.Cursor
import fr.mobdev.peertubelive.objects.OAuthData import fr.mobdev.peertubelive.objects.OAuthData
import fr.mobdev.peertubelive.objects.StreamData
import fr.mobdev.peertubelive.objects.StreamSettings import fr.mobdev.peertubelive.objects.StreamSettings
object DatabaseManager { object DatabaseManager {
@ -103,8 +104,9 @@ object DatabaseManager {
val comments: Boolean = cursor.getInt(col++) == 1 val comments: Boolean = cursor.getInt(col++) == 1
val download: Boolean = cursor.getInt(col++) == 1 val download: Boolean = cursor.getInt(col++) == 1
val saveReplay: Boolean = cursor.getInt(col++) == 1 val saveReplay: Boolean = cursor.getInt(col++) == 1
val nsfw: 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) val resolution: StreamData.STREAM_RESOLUTION = StreamData.STREAM_RESOLUTION.values()[cursor.getInt(col)]
streamSettings = StreamSettings(title,0,privacy,category,language,licence,null,comments,download,nsfw,saveReplay,resolution)
} }
cursor?.close() cursor?.close()
return streamSettings return streamSettings
@ -123,6 +125,7 @@ object DatabaseManager {
values.put(DatabaseHelper.SETS_REPLAY,streamSettings.saveReplay) values.put(DatabaseHelper.SETS_REPLAY,streamSettings.saveReplay)
values.put(DatabaseHelper.SETS_LANGUAGE,streamSettings.language) values.put(DatabaseHelper.SETS_LANGUAGE,streamSettings.language)
values.put(DatabaseHelper.SETS_LICENCE,streamSettings.licence) values.put(DatabaseHelper.SETS_LICENCE,streamSettings.licence)
values.put(DatabaseHelper.SETS_RESOLUTION,streamSettings.resolution.ordinal)
val whereClause = "id = ?" val whereClause = "id = ?"

View File

@ -422,7 +422,7 @@ object InstanceManager {
val rtmp = json.getString(RTMP_URL) val rtmp = json.getString(RTMP_URL)
val key = json.getString(STREAM_KEY) val key = json.getString(STREAM_KEY)
StreamData(rtmp,key) StreamData(rtmp,key,null)
} else { } else {
null null

View File

@ -3,10 +3,11 @@ package fr.mobdev.peertubelive.objects
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
class StreamData(val url: String?, val key: String?): Parcelable { class StreamData(val url: String?, val key: String?, val resolution: STREAM_RESOLUTION?): Parcelable {
constructor(parcel: Parcel) : this( constructor(parcel: Parcel) : this(
parcel.readString(), parcel.readString(),
parcel.readString() parcel.readString(),
STREAM_RESOLUTION.values()[(parcel.readInt())]
) { ) {
} }
@ -17,6 +18,7 @@ class StreamData(val url: String?, val key: String?): Parcelable {
override fun writeToParcel(dest: Parcel, flags: Int) { override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeString(url) dest.writeString(url)
dest.writeString(key) dest.writeString(key)
dest.writeInt(resolution?.ordinal!!)
} }
companion object CREATOR : Parcelable.Creator<StreamData> { companion object CREATOR : Parcelable.Creator<StreamData> {
@ -29,4 +31,14 @@ class StreamData(val url: String?, val key: String?): Parcelable {
} }
} }
enum class STREAM_RESOLUTION
{
p2160,
p1440,
p1080,
p720,
p480,
p360
}
} }

View File

@ -5,7 +5,7 @@ import android.os.Parcelable
class StreamSettings( class StreamSettings(
val title: String, val channel: Long, val privacy: Int, val category: Int?, val language: String?, val licence: Int?, val description: String?, 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 { val comments: Boolean, val download: Boolean, val nsfw: Boolean, val saveReplay: Boolean, val resolution: StreamData.STREAM_RESOLUTION) : Parcelable {
constructor(parcel: Parcel) : this( constructor(parcel: Parcel) : this(
parcel.readString()!!, parcel.readString()!!,
parcel.readLong(), parcel.readLong(),
@ -17,7 +17,8 @@ class StreamSettings(
parcel.readByte() != 0.toByte(), parcel.readByte() != 0.toByte(),
parcel.readByte() != 0.toByte(), parcel.readByte() != 0.toByte(),
parcel.readByte() != 0.toByte(), parcel.readByte() != 0.toByte(),
parcel.readByte() != 0.toByte() parcel.readByte() != 0.toByte(),
StreamData.STREAM_RESOLUTION.values()[(parcel.readInt())]
) { ) {
} }
@ -37,6 +38,7 @@ class StreamSettings(
parcel.writeByte(if (download) 1 else 0) parcel.writeByte(if (download) 1 else 0)
parcel.writeByte(if (nsfw) 1 else 0) parcel.writeByte(if (nsfw) 1 else 0)
parcel.writeByte(if (saveReplay) 1 else 0) parcel.writeByte(if (saveReplay) 1 else 0)
parcel.writeInt(resolution?.ordinal!!)
} }
override fun describeContents(): Int { override fun describeContents(): Int {

View File

@ -127,12 +127,32 @@
android:layout_margin="5dp" android:layout_margin="5dp"
/> />
<TextView
android:id="@+id/resolution"
android:text="@string/stream_resolution"
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"/>
<Spinner
android:id="@+id/resolution_list"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:entries="@array/stream_resolution"
app:layout_constraintTop_toBottomOf="@id/resolution"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_margin="5dp"
/>
<LinearLayout <LinearLayout
android:id="@+id/save_replay_layout" android:id="@+id/save_replay_layout"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
app:layout_constraintTop_toBottomOf="@id/privacy_list" app:layout_constraintTop_toBottomOf="@id/resolution_list"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
> >

View File

@ -25,6 +25,7 @@
<string name="lock_reason">Votre direct s\'est terminé car le téléphone a été verrouillé</string> <string name="lock_reason">Votre direct s\'est terminé car le téléphone a été verrouillé</string>
<string name="stop_reason">Votre direct est terminé</string> <string name="stop_reason">Votre direct est terminé</string>
<string name="ask_end_stream">Voulez-vous arrêter le direct\?</string> <string name="ask_end_stream">Voulez-vous arrêter le direct\?</string>
<string name="network_reason">Votre direct s\'est terminé à cause d\'un problème réseau</string>
<!-- buttons --> <!-- buttons -->
<string name="choose_channel">Chaîne</string> <string name="choose_channel">Chaîne</string>
<string name="go_live">Démarrer le direct!</string> <string name="go_live">Démarrer le direct!</string>
@ -55,6 +56,7 @@
<string name="stream_ended">Direct terminé</string> <string name="stream_ended">Direct terminé</string>
<string name="end_stream">Arrêter le direct</string> <string name="end_stream">Arrêter le direct</string>
<string name="creating">Patientez pendant la creation du direct</string> <string name="creating">Patientez pendant la creation du direct</string>
<string name="stream_resolution">Résolution du direct</string>
<!-- placeholders --> <!-- placeholders -->
<string name="exemple_instance">ex: peertube.fr, https://peertube.fr</string> <string name="exemple_instance">ex: peertube.fr, https://peertube.fr</string>
<!-- category --> <!-- category -->

View File

@ -20,11 +20,12 @@
<string name="delete_account">Delete the %s account associated with the %s server\?</string> <string name="delete_account">Delete the %s account associated with the %s server\?</string>
<string name="tags_rules">Maximum 5 tags, each between 2 and 30 characters, separate by comma</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="save_replay_info">If you enable this option, your live will be terminated if you exceed your video quota</string>
<string name="back_reason">Your live has ended after you pressed back button</string> <string name="back_reason">Your livestream ended after you pressed back button</string>
<string name="background_reason">Your live has ended because the app has gone to background</string> <string name="background_reason">Your livestream ended because the app has gone to background</string>
<string name="lock_reason">Your live has ended because the phone was locked</string> <string name="lock_reason">Your livestream ended because the phone was locked</string>
<string name="stop_reason">Your live has ended</string> <string name="stop_reason">Your livestream ended</string>
<string name="ask_end_stream">Do you want to stop the live?</string> <string name="ask_end_stream">Do you want to stop the live?</string>
<string name="network_reason">Your livestream ended because of a network problem</string>
<!-- buttons --> <!-- buttons -->
<string name="choose_channel">Channel</string> <string name="choose_channel">Channel</string>
<string name="go_live">Start livestream</string> <string name="go_live">Start livestream</string>
@ -55,6 +56,7 @@
<string name="stream_ended">Livestream ended</string> <string name="stream_ended">Livestream ended</string>
<string name="end_stream">Stop livestream</string> <string name="end_stream">Stop livestream</string>
<string name="creating">Wait for live creation</string> <string name="creating">Wait for live creation</string>
<string name="stream_resolution">Livestream Resolution</string>
<!-- placeholders --> <!-- placeholders -->
<string name="exemple_instance">e.g. peertube.fr, https://peertube.fr</string> <string name="exemple_instance">e.g. peertube.fr, https://peertube.fr</string>
<!-- category --> <!-- category -->
@ -283,4 +285,13 @@
<string name="unlisted">Unlisted</string> <string name="unlisted">Unlisted</string>
<string name="privacy_private">Private</string> <string name="privacy_private">Private</string>
<string name="internal">Internal</string> <string name="internal">Internal</string>
<array name="stream_resolution">
<item>2160p</item>
<item>1440p</item>
<item>1080p</item>
<item>720p</item>
<item>480p</item>
<item>360p</item>
</array>
</resources> </resources>