1
0
Fork 0
mirror of https://gitlab.com/futo-org/fcast.git synced 2025-07-29 13:27:00 +00:00

Merge branch 'michael/android' into 'master'

Android update 42

See merge request videostreaming/fcast!17
This commit is contained in:
Michael Hollister 2025-07-14 09:28:29 -05:00
commit b0395ecf98
28 changed files with 363 additions and 128 deletions

View file

@ -7,9 +7,14 @@
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
/.idea/*
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
/app/defaultFlavor
/app/playstore

View file

@ -1,7 +1,49 @@
buildAndDeployAndroid:
stage: buildAndDeployAndroid
buildAndroidDockerContainer:
stage: buildDockerContainers
image: docker:20.10.16
services:
- docker:20.10.16-dind
tags:
- fcast-instance-runner
before_script:
- cd receivers/android
script:
- sh deploy.sh
- echo "$CI_REGISTRY_PASSWORD" | docker login $CI_REGISTRY -u $CI_REGISTRY_USER --password-stdin
- docker build -t $CI_REGISTRY/videostreaming/fcast/receiver-android-dev:latest .
- docker push $CI_REGISTRY/videostreaming/fcast/receiver-android-dev:latest
when: manual
buildAndroid:
stage: buildAndDeployAndroid
image: gitlab.futo.org:5050/videostreaming/fcast/receiver-android-dev:latest
tags:
- fcast-instance-runner
variables:
ANDROID_VERSION_NAME: "1"
ANDROID_VERSION_CODE: "1"
before_script:
- cd receivers/android
script:
- echo "Building content..."
- ./gradlew --stacktrace assembleRelease -PversionName=$ANDROID_VERSION_NAME -PversionCode=$ANDROID_VERSION_CODE
- ./gradlew --stacktrace bundlePlaystoreRelease -PversionName=$ANDROID_VERSION_NAME -PversionCode=$ANDROID_VERSION_CODE
- echo $ANDROID_VERSION_CODE > ./fcast-version.txt
- mkdir -p /artifacts/$ANDROID_VERSION_CODE
- cp -rf ./app/build/outputs/apk/defaultFlavor/release/app-defaultFlavor-release.apk /artifacts/$ANDROID_VERSION_CODE/fcast-release.apk
- cp -rf ./app/build/outputs/bundle/playstoreRelease/app-playstore-release.aab /artifacts/$ANDROID_VERSION_CODE/fcast-playstore-release.aab
- cp -rf ./fcast-version.txt /artifacts/fcast-version.txt
# Artifact uploads require artifacts to be in project directory
- mkdir -p ./$ANDROID_VERSION_CODE
- mv ./app/build/outputs/apk/defaultFlavor/release/app-defaultFlavor-release.apk ./$ANDROID_VERSION_CODE/fcast-release.apk
- mv ./app/build/outputs/bundle/playstoreRelease/app-playstore-release.aab ./$ANDROID_VERSION_CODE/fcast-playstore-release.aab
artifacts:
untracked: false
when: on_success
access: all
expire_in: "30 days"
paths:
- receivers/android/$ANDROID_VERSION_CODE/fcast-release.apk
- receivers/android/$ANDROID_VERSION_CODE/fcast-playstore-release.aab
when: manual

View file

@ -0,0 +1,19 @@
FROM ubuntu:24.04
# TZ
ARG DEBIAN_FRONTEND=noninteractive
ENV ANDROID_HOME=/Android/Sdk
ENV TZ=Etc/UTC
RUN apt update
RUN apt install -y zip wget tzdata
RUN apt install -y openjdk-21-jdk
RUN wget https://dl.google.com/android/repository/commandlinetools-linux-13114758_latest.zip
RUN unzip commandlinetools-linux-13114758_latest.zip
RUN mkdir -p $ANDROID_HOME
RUN mv /cmdline-tools $ANDROID_HOME
RUN yes | $ANDROID_HOME/cmdline-tools/bin/sdkmanager --sdk_root=$ANDROID_HOME --licenses
RUN $ANDROID_HOME/cmdline-tools/bin/sdkmanager --sdk_root=$ANDROID_HOME --install "platforms;android-36"

View file

@ -1,7 +1,7 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'org.jetbrains.kotlin.plugin.serialization' version '1.8.10'
id 'org.jetbrains.kotlin.plugin.serialization' version '2.2.0'
}
ext {
@ -13,19 +13,19 @@ println("Version Name: $currentVersionName")
println("Version Code: $currentVersionCode")
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('/opt/key.properties')
def keystorePropertiesFile = rootProject.file('/certs/key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
android {
namespace 'com.futo.fcast.receiver'
compileSdk 34
compileSdk 36
defaultConfig {
applicationId "com.futo.fcast.receiver"
minSdk 24
targetSdk 34
targetSdk 36
versionCode currentVersionCode
versionName currentVersionName
@ -69,11 +69,8 @@ android {
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
sourceCompatibility JavaVersion.VERSION_21
targetCompatibility JavaVersion.VERSION_21
}
buildFeatures {
buildConfig true
@ -84,18 +81,18 @@ android {
}
dependencies {
implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.11.0'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2"
implementation 'androidx.media3:media3-exoplayer:1.2.0'
implementation "com.squareup.okhttp3:okhttp:4.11.0"
implementation 'androidx.core:core-ktx:1.16.0'
implementation 'androidx.appcompat:appcompat:1.7.1'
implementation 'com.google.android.material:material:1.12.0'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0"
implementation 'androidx.media3:media3-exoplayer:1.7.1'
implementation "com.squareup.okhttp3:okhttp:5.1.0"
implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
implementation 'org.java-websocket:Java-WebSocket:1.5.4'
implementation 'androidx.media3:media3-ui:1.2.0'
implementation 'androidx.media3:media3-exoplayer-dash:1.2.0'
implementation 'androidx.media3:media3-exoplayer-hls:1.2.0'
implementation 'org.java-websocket:Java-WebSocket:1.6.0'
implementation 'androidx.media3:media3-ui:1.7.1'
implementation 'androidx.media3:media3-exoplayer-dash:1.7.1'
implementation 'androidx.media3:media3-exoplayer-hls:1.7.1'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
}

View file

@ -19,7 +19,7 @@ class BootReceiver : BroadcastReceiver() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// Show a notification with an action to start the service
showStartServiceNotification(context);
showStartServiceNotification(context)
} else {
// Directly start the service for older versions
val serviceIntent = Intent(context, NetworkService::class.java)

View file

@ -4,4 +4,4 @@ import android.content.Context
import android.util.AttributeSet
import androidx.media3.ui.PlayerView
class CustomPlayerView(context: Context, attrs: AttributeSet? = null) : PlayerView(context, attrs) { }
class CustomPlayerView(context: Context, attrs: AttributeSet? = null) : PlayerView(context, attrs)

View file

@ -1,6 +1,5 @@
package com.futo.fcast.receiver
import WebSocketListenerService
import android.content.Context
import android.net.nsd.NsdManager
import android.net.nsd.NsdServiceInfo
@ -40,14 +39,14 @@ class DiscoveryService(private val _context: Context) {
try {
_nsdManager?.unregisterService(_registrationListenerTcp)
} catch (e: Throwable) {
Log.e(TAG, "Failed to unregister TCP Listener.");
} catch (_: Throwable) {
Log.e(TAG, "Failed to unregister TCP Listener.")
}
try {
_nsdManager?.unregisterService(_registrationListenerWs)
} catch (e: Throwable) {
Log.e(TAG, "Failed to unregister TCP Listener.");
} catch (_: Throwable) {
Log.e(TAG, "Failed to unregister TCP Listener.")
}
_nsdManager = null

View file

@ -1,27 +1,13 @@
package com.futo.fcast.receiver
import android.util.Base64
import android.util.Log
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.io.DataOutputStream
import java.io.OutputStream
import java.math.BigInteger
import java.net.SocketAddress
import java.nio.ByteBuffer
import java.security.KeyFactory
import java.security.KeyPair
import java.security.KeyPairGenerator
import java.security.MessageDigest
import java.security.PrivateKey
import java.security.spec.X509EncodedKeySpec
import java.util.UUID
import javax.crypto.Cipher
import javax.crypto.KeyAgreement
import javax.crypto.spec.DHParameterSpec
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
enum class SessionState {
@ -48,8 +34,8 @@ enum class Opcode(val value: Byte) {
Pong(13);
companion object {
private val _map = values().associateBy { it.value }
fun find(value: Byte): Opcode = _map[value] ?: Opcode.None
private val _map = entries.associateBy { it.value }
fun find(value: Byte): Opcode = _map[value] ?: None
}
}
@ -111,7 +97,7 @@ class FCastSession(outputStream: OutputStream, private val _remoteSocketAddress:
}
fun processBytes(data: ByteBuffer) {
Log.i(TAG, "${data.remaining()} bytes received from ${_remoteSocketAddress}")
Log.i(TAG, "${data.remaining()} bytes received from $_remoteSocketAddress")
if (!data.hasArray()) {
throw IllegalArgumentException("ByteBuffer does not have a backing array")
}
@ -132,7 +118,7 @@ class FCastSession(outputStream: OutputStream, private val _remoteSocketAddress:
return
}
Log.i(TAG, "$count bytes received from ${_remoteSocketAddress}")
Log.i(TAG, "$count bytes received from $_remoteSocketAddress")
when (_state) {
SessionState.WaitingForLength -> handleLengthBytes(data, 0, count)
@ -166,7 +152,7 @@ class FCastSession(outputStream: OutputStream, private val _remoteSocketAddress:
}
if (bytesRemaining > 0) {
Log.i(TAG, "$bytesRemaining remaining bytes ${_remoteSocketAddress} pushed to handlePacketBytes")
Log.i(TAG, "$bytesRemaining remaining bytes $_remoteSocketAddress pushed to handlePacketBytes")
handlePacketBytes(data, offset + bytesToRead, bytesRemaining)
}
}
@ -181,7 +167,7 @@ class FCastSession(outputStream: OutputStream, private val _remoteSocketAddress:
Log.i(TAG, "Read $bytesToRead bytes from packet")
if (_bytesRead >= _packetLength) {
Log.i(TAG, "Packet finished receiving from ${_remoteSocketAddress} of $_packetLength bytes.")
Log.i(TAG, "Packet finished receiving from $_remoteSocketAddress of $_packetLength bytes.")
handleNextPacket()
_state = SessionState.WaitingForLength
@ -189,14 +175,14 @@ class FCastSession(outputStream: OutputStream, private val _remoteSocketAddress:
_bytesRead = 0
if (bytesRemaining > 0) {
Log.i(TAG, "$bytesRemaining remaining bytes ${_remoteSocketAddress} pushed to handleLengthBytes")
Log.i(TAG, "$bytesRemaining remaining bytes $_remoteSocketAddress pushed to handleLengthBytes")
handleLengthBytes(data, offset + bytesToRead, bytesRemaining)
}
}
}
private fun handleNextPacket() {
Log.i(TAG, "Processing packet of $_bytesRead bytes from ${_remoteSocketAddress}")
Log.i(TAG, "Processing packet of $_bytesRead bytes from $_remoteSocketAddress")
val opcode = Opcode.find(_buffer[0])
val body = if (_packetLength > 1) _buffer.copyOfRange(1, _packetLength)

View file

@ -1,16 +1,12 @@
package com.futo.fcast.receiver
import WebSocketListenerService
import android.Manifest
import android.app.Activity
import android.app.AlertDialog
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInstaller
import android.content.pm.PackageManager
import android.graphics.drawable.Animatable
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
@ -34,12 +30,12 @@ import androidx.media3.ui.PlayerView
import com.google.zxing.BarcodeFormat
import com.journeyapps.barcodescanner.BarcodeEncoder
import kotlinx.coroutines.*
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import okhttp3.OkHttpClient
import java.io.InputStream
import java.io.OutputStream
import java.net.NetworkInterface
import androidx.core.net.toUri
class MainActivity : AppCompatActivity() {
@ -153,7 +149,7 @@ class MainActivity : AppCompatActivity() {
Log.i(TAG, "connection url: $url")
val bitmap = barcodeEncoder.encodeBitmap(url, BarcodeFormat.QR_CODE, px, px)
_imageQr.setImageBitmap(bitmap)
} catch (e: java.lang.Exception) {
} catch (_: java.lang.Exception) {
_textScanToConnect.visibility = View.GONE
_imageQr.visibility = View.GONE
}
@ -201,7 +197,7 @@ class MainActivity : AppCompatActivity() {
_player = ExoPlayer.Builder(this).build()
_videoBackground.player = _player
val mediaItem = MediaItem.fromUri(Uri.parse("android.resource://" + packageName + "/" + R.raw.c))
val mediaItem = MediaItem.fromUri(("android.resource://" + packageName + "/" + R.raw.c).toUri())
_player.setMediaItem(mediaItem)
_player.prepare()
_player.repeatMode = Player.REPEAT_MODE_ALL
@ -224,7 +220,7 @@ class MainActivity : AppCompatActivity() {
if (listPermissionsNeeded.isNotEmpty()) {
val permissionRequestedKey = "NOTIFICATIONS_PERMISSION_REQUESTED"
val sharedPref = this.getSharedPreferences(_preferenceFileKey, Context.MODE_PRIVATE)
val sharedPref = this.getSharedPreferences(_preferenceFileKey, MODE_PRIVATE)
val hasRequestedPermission = sharedPref.getBoolean(permissionRequestedKey, false)
if (!hasRequestedPermission) {
ActivityCompat.requestPermissions(this, listPermissionsNeeded.toTypedArray(), REQUEST_ID_MULTIPLE_PERMISSIONS)
@ -244,7 +240,7 @@ class MainActivity : AppCompatActivity() {
private fun requestSystemAlertWindowPermission() {
try {
val permissionRequestedKey = "SYSTEM_ALERT_WINDOW_PERMISSION_REQUESTED"
val sharedPref = this.getSharedPreferences(_preferenceFileKey, Context.MODE_PRIVATE)
val sharedPref = this.getSharedPreferences(_preferenceFileKey, MODE_PRIVATE)
val hasRequestedPermission = sharedPref.getBoolean(permissionRequestedKey, false)
if (!Settings.canDrawOverlays(this)) {
@ -254,7 +250,8 @@ class MainActivity : AppCompatActivity() {
.setMessage(R.string.permission_dialog_message)
.setPositiveButton(R.string.permission_dialog_positive_button) { _, _ ->
try {
val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:$packageName"))
val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
"package:$packageName".toUri())
_systemAlertWindowPermissionLauncher.launch(intent)
} catch (e: Throwable) {
Log.e("OverlayPermission", "Error requesting overlay permission", e)
@ -276,7 +273,7 @@ class MainActivity : AppCompatActivity() {
Toast.makeText(this, "Optional system alert window permission missing", Toast.LENGTH_SHORT).show()
}
}
} catch (e: Throwable) {
} catch (_: Throwable) {
Log.e(TAG, "Failed to request system alert window permissions")
}
}
@ -367,7 +364,7 @@ class MainActivity : AppCompatActivity() {
_updateSpinner.visibility = View.INVISIBLE
setText(resources.getText(R.string.there_is_an_update_available_do_you_wish_to_update))
_buttonUpdate.visibility = View.VISIBLE
} catch (e: Throwable) {
} catch (_: Throwable) {
Toast.makeText(this@MainActivity, "Failed to show update dialog", Toast.LENGTH_LONG).show()
Log.w(TAG, "Error occurred in update dialog.")
}
@ -388,11 +385,11 @@ class MainActivity : AppCompatActivity() {
.build()
val response = client.newCall(request).execute()
if (!response.isSuccessful || response.body == null) {
if (!response.isSuccessful) {
return null
}
return response.body?.string()?.trim()?.toInt()
return response.body.string().trim().toInt()
}
private fun update() {
@ -414,7 +411,7 @@ class MainActivity : AppCompatActivity() {
val response = client.newCall(request).execute()
val body = response.body
if (response.isSuccessful && body != null) {
if (response.isSuccessful) {
inputStream = body.byteStream()
val dataLength = body.contentLength()
install(inputStream, dataLength)
@ -540,8 +537,8 @@ class MainActivity : AppCompatActivity() {
companion object {
const val TAG = "MainActivity"
const val VERSION_URL = "https://releases.grayjay.app/fcast-version.txt"
const val APK_URL = "https://releases.grayjay.app/fcast-release.apk"
const val VERSION_URL = "https://dl.fcast.org/android/fcast-version.txt"
const val APK_URL = "https://dl.fcast.org/android/fcast-release.apk"
const val REQUEST_ID_MULTIPLE_PERMISSIONS = 1
const val REQUEST_CODE = 2
}

View file

@ -1,8 +1,6 @@
package com.futo.fcast.receiver
import WebSocketListenerService
import android.app.*
import android.content.Context
import android.content.Intent
import android.content.pm.ServiceInfo
import android.os.Build
@ -10,10 +8,8 @@ import android.os.IBinder
import android.provider.Settings
import android.util.Log
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import kotlinx.coroutines.*
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
class NetworkService : Service() {
@ -48,7 +44,7 @@ class NetworkService : Service() {
description = descriptionText
}
val notificationManager: NotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val notificationManager: NotificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
@ -72,7 +68,7 @@ class NetworkService : Service() {
try {
Log.i(TAG, "Sending version ${session.id}")
session.send(Opcode.Version, VersionMessage(2))
} catch (e: Throwable) {
} catch (_: Throwable) {
Log.e(TAG, "Failed to send version ${session.id}")
}
}
@ -122,7 +118,7 @@ class NetworkService : Service() {
try {
_webSocketListenerService?.stop()
} catch (e: Throwable) {
} catch (_: Throwable) {
//Ignored
} finally {
_webSocketListenerService = null
@ -193,7 +189,7 @@ class NetworkService : Service() {
.setAutoCancel(true)
.build()
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
notificationManager.notify(PLAY_NOTIFICATION_ID, playNotification)
}
} else {

View file

@ -1,6 +1,5 @@
package com.futo.fcast.receiver
import android.content.Context
import android.graphics.drawable.Animatable
import android.net.ConnectivityManager
import android.net.Network
@ -44,6 +43,7 @@ import java.io.File
import java.io.FileOutputStream
import kotlin.math.abs
import kotlin.math.max
import androidx.core.net.toUri
class PlayerActivity : AppCompatActivity() {
@ -245,7 +245,7 @@ class PlayerActivity : AppCompatActivity() {
_playerControlView.controllerAutoShow = false
Log.i(TAG, "Attached onConnectionAvailable listener.")
_connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
_connectivityManager = getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
val netReq = NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
@ -398,7 +398,7 @@ class PlayerActivity : AppCompatActivity() {
}
if (!playMessage.url.isNullOrEmpty()) {
mediaItemBuilder.setUri(Uri.parse(playMessage.url))
mediaItemBuilder.setUri(playMessage.url.toUri())
} else if (!playMessage.content.isNullOrEmpty()) {
val tempFile = File.createTempFile("content_", ".tmp", cacheDir)
tempFile.deleteOnExit()

View file

@ -1,7 +1,6 @@
package com.futo.fcast.receiver
import android.util.Log
import java.io.BufferedInputStream
import java.net.InetSocketAddress
import java.net.ServerSocket
import java.net.Socket

View file

@ -1,6 +1,6 @@
package com.futo.fcast.receiver
import android.util.Log
import com.futo.fcast.receiver.FCastSession
import com.futo.fcast.receiver.NetworkService
import org.java_websocket.WebSocket
import org.java_websocket.handshake.ClientHandshake
import org.java_websocket.server.WebSocketServer

View file

@ -1,3 +1,5 @@
package com.futo.fcast.receiver
import org.java_websocket.WebSocket
import java.io.IOException
import java.io.OutputStream

View file

@ -0,0 +1,25 @@
<resources>
<string name="app_name">جهاز استقبال FCast</string>
<string name="general_failure">فشلت العملية بشكل عام</string>
<string name="aborted">فشلت العملية بسبب إلغائها بشكل نشط</string>
<string name="blocked">فشلت العملية بسبب حظرها</string>
<string name="conflict">فشلت العملية لأنها تتعارض (أو غير متوافقة مع) حزمة أخرى مثبتة بالفعل على الجهاز</string>
<string name="incompatible">فشلت العملية لأنها غير متوافقة بشكل أساسي مع هذا الجهاز</string>
<string name="invalid">فشلت العملية لأن أحد ملفات APK أو أكثر كان غير صالح</string>
<string name="not_enough_storage">فشلت العملية بسبب مشاكل التخزين</string>
<string name="downloading_update">جاري تنزيل التحديث…</string>
<string name="installing_update">جاري تثبيت التحديث…</string>
<string name="success">نجاح</string>
<string name="failed_to_update_with_error">فشل تحديث الحزمة بسبب الخطأ</string>
<string name="there_is_an_update_available_do_you_wish_to_update">هناك تحديث متاح لجهاز الاستقبال FCast، هل ترغب في التحديث؟</string>
<string name="never">أبداً</string>
<string name="close">يغلق</string>
<string name="update">تحديث</string>
<string name="checking_for_updates">التحقق من التحديثات…</string>
<string name="no_updates_available">لا توجد تحديثات متاحة</string>
<string name="permission_dialog_title">إذن نافذة تنبيه النظام</string>
<string name="permission_dialog_message">يتطلب هذا التطبيق إذن "نافذة تنبيه النظام" لعرض المحتوى فوق التطبيقات الأخرى. يُرجى منح الإذن.</string>
<string name="permission_dialog_positive_button">يسمح</string>
<string name="permission_dialog_negative_button">يلغي</string>
<string name="waiting_for_media">في انتظار وسائل الإعلام</string>
</resources>

View file

@ -0,0 +1,25 @@
<resources>
<string name="app_name">FCast-Empfänger</string>
<string name="general_failure">Der Vorgang ist auf allgemeine Weise fehlgeschlagen</string>
<string name="aborted">Der Vorgang ist fehlgeschlagen, da er aktiv abgebrochen wurde</string>
<string name="blocked">Der Vorgang ist fehlgeschlagen, weil er blockiert wurde</string>
<string name="conflict">Der Vorgang ist fehlgeschlagen, da er mit einem anderen Paket, das bereits auf dem Gerät installiert ist, in Konflikt steht (oder inkonsistent ist).</string>
<string name="incompatible">Der Vorgang ist fehlgeschlagen, da er grundsätzlich nicht mit diesem Gerät kompatibel ist</string>
<string name="invalid">Der Vorgang ist fehlgeschlagen, da eine oder mehrere APKs ungültig waren</string>
<string name="not_enough_storage">Der Vorgang ist aufgrund von Speicherproblemen fehlgeschlagen</string>
<string name="downloading_update">Update wird heruntergeladen…</string>
<string name="installing_update">Update wird installiert…</string>
<string name="success">Erfolg</string>
<string name="failed_to_update_with_error">Paket konnte nicht aktualisiert werden. Fehler</string>
<string name="there_is_an_update_available_do_you_wish_to_update">Für den FCast-Receiver ist ein Update verfügbar. Möchten Sie ein Update durchführen?</string>
<string name="never">Niemals</string>
<string name="close">Schließen</string>
<string name="update">Aktualisieren</string>
<string name="checking_for_updates">Suche nach Updates…</string>
<string name="no_updates_available">Keine Updates verfügbar</string>
<string name="permission_dialog_title">Berechtigung für das Systemwarnungsfenster</string>
<string name="permission_dialog_message">Diese App benötigt die Berechtigung „Systemwarnfenster“, um Inhalte über anderen Apps anzuzeigen. Bitte erteilen Sie die Berechtigung.</string>
<string name="permission_dialog_positive_button">Erlauben</string>
<string name="permission_dialog_negative_button">Stornieren</string>
<string name="waiting_for_media">Warten auf Medien</string>
</resources>

View file

@ -0,0 +1,25 @@
<resources>
<string name="app_name">Receptor FCast</string>
<string name="general_failure">La operación falló de forma genérica</string>
<string name="aborted">La operación falló porque fue abortada activamente.</string>
<string name="blocked">La operación falló porque fue bloqueada</string>
<string name="conflict">La operación falló porque entra en conflicto (o es inconsistente) con otro paquete ya instalado en el dispositivo</string>
<string name="incompatible">La operación falló porque es fundamentalmente incompatible con este dispositivo.</string>
<string name="invalid">La operación falló porque uno o más de los APK no eran válidos</string>
<string name="not_enough_storage">La operación falló debido a problemas de almacenamiento.</string>
<string name="downloading_update">Descargando actualización…</string>
<string name="installing_update">Instalando actualización…</string>
<string name="success">Éxito</string>
<string name="failed_to_update_with_error">No se pudo actualizar el paquete con error</string>
<string name="there_is_an_update_available_do_you_wish_to_update">Hay una actualización disponible para el receptor FCast, ¿desea actualizar?</string>
<string name="never">Nunca</string>
<string name="close">Cerca</string>
<string name="update">Actualizar</string>
<string name="checking_for_updates">Buscando actualizaciones…</string>
<string name="no_updates_available">No hay actualizaciones disponibles</string>
<string name="permission_dialog_title">Permiso de la ventana de alerta del sistema</string>
<string name="permission_dialog_message">Esta aplicación requiere el permiso de la ventana de alertas del sistema para mostrar contenido sobre otras aplicaciones. Por favor, concédelo.</string>
<string name="permission_dialog_positive_button">Permitir</string>
<string name="permission_dialog_negative_button">Cancelar</string>
<string name="waiting_for_media">Esperando a los medios</string>
</resources>

View file

@ -0,0 +1,25 @@
<resources>
<string name="app_name">Récepteur FCast</string>
<string name="general_failure">L\'opération a échoué de manière générique</string>
<string name="aborted">L\'opération a échoué car elle a été activement interrompue</string>
<string name="blocked">L\'opération a échoué car elle a été bloquée</string>
<string name="conflict">L\'opération a échoué car elle est en conflit (ou est incohérente avec) avec un autre package déjà installé sur l\'appareil</string>
<string name="incompatible">L\'opération a échoué car elle est fondamentalement incompatible avec cet appareil</string>
<string name="invalid">L\'opération a échoué car un ou plusieurs APK n\'étaient pas valides</string>
<string name="not_enough_storage">L\'opération a échoué en raison de problèmes de stockage</string>
<string name="downloading_update">Téléchargement de la mise à jour…</string>
<string name="installing_update">Installation de la mise à jour…</string>
<string name="success">Succès</string>
<string name="failed_to_update_with_error">Échec de la mise à jour du package avec erreur</string>
<string name="there_is_an_update_available_do_you_wish_to_update">Une mise à jour est disponible pour le récepteur FCast, souhaitez-vous effectuer la mise à jour ?</string>
<string name="never">Jamais</string>
<string name="close">Fermer</string>
<string name="update">Mise à jour</string>
<string name="checking_for_updates">Vérification des mises à jour…</string>
<string name="no_updates_available">Aucune mise à jour disponible</string>
<string name="permission_dialog_title">Autorisation de la fenêtre d\'alerte système</string>
<string name="permission_dialog_message">Cette application nécessite l\'autorisation de la fenêtre d\'alerte système pour afficher du contenu par-dessus d\'autres applications. Veuillez accorder cette autorisation.</string>
<string name="permission_dialog_positive_button">Permettre</string>
<string name="permission_dialog_negative_button">Annuler</string>
<string name="waiting_for_media">En attente des médias</string>
</resources>

View file

@ -0,0 +1,25 @@
<resources>
<string name="app_name">FCastレシーバー</string>
<string name="general_failure">操作は一般的な方法で失敗しました</string>
<string name="aborted">操作は強制的に中止されたため失敗しました</string>
<string name="blocked">ブロックされたため操作に失敗しました</string>
<string name="conflict">デバイスにすでにインストールされている別のパッケージと競合(または矛盾)しているため、操作に失敗しました</string>
<string name="incompatible">このデバイスと根本的に互換性がないため、操作は失敗しました</string>
<string name="invalid">1 つ以上の APK が無効であったため、操作に失敗しました</string>
<string name="not_enough_storage">ストレージの問題により操作が失敗しました</string>
<string name="downloading_update">アップデートをダウンロードしています…</string>
<string name="installing_update">アップデートをインストールしています…</string>
<string name="success">成功</string>
<string name="failed_to_update_with_error">エラーのためパッケージの更新に失敗しました</string>
<string name="there_is_an_update_available_do_you_wish_to_update">FCast レシーバーのアップデートが利用可能です。アップデートしますか?</string>
<string name="never">一度もない</string>
<string name="close">近い</string>
<string name="update">アップデート</string>
<string name="checking_for_updates">アップデートを確認しています…</string>
<string name="no_updates_available">更新情報はありません</string>
<string name="permission_dialog_title">システム警告ウィンドウの権限</string>
<string name="permission_dialog_message">このアプリは、他のアプリの上にコンテンツを表示するためにシステムアラートウィンドウの権限が必要です。権限を付与してください。</string>
<string name="permission_dialog_positive_button">許可する</string>
<string name="permission_dialog_negative_button">キャンセル</string>
<string name="waiting_for_media">メディアを待っています</string>
</resources>

View file

@ -0,0 +1,25 @@
<resources>
<string name="app_name">FCast 수신기</string>
<string name="general_failure">작업이 일반적인 방식으로 실패했습니다.</string>
<string name="aborted">작업이 적극적으로 중단되어 실패했습니다.</string>
<string name="blocked">작업이 차단되어 실패했습니다.</string>
<string name="conflict">해당 작업은 장치에 이미 설치된 다른 패키지와 충돌하거나 일관성이 없기 때문에 실패했습니다.</string>
<string name="incompatible">이 작업은 이 장치와 근본적으로 호환되지 않기 때문에 실패했습니다.</string>
<string name="invalid">APK 중 하나 이상이 유효하지 않아 작업이 실패했습니다.</string>
<string name="not_enough_storage">저장소 문제로 인해 작업이 실패했습니다.</string>
<string name="downloading_update">업데이트를 다운로드하는 중…</string>
<string name="installing_update">업데이트를 설치하는 중…</string>
<string name="success">성공</string>
<string name="failed_to_update_with_error">오류로 인해 패키지를 업데이트하지 못했습니다.</string>
<string name="there_is_an_update_available_do_you_wish_to_update">FCast 수신기에 대한 업데이트가 있습니다. 업데이트하시겠습니까?</string>
<string name="never">절대</string>
<string name="close">닫다</string>
<string name="update">업데이트</string>
<string name="checking_for_updates">업데이트를 확인하는 중…</string>
<string name="no_updates_available">업데이트가 없습니다</string>
<string name="permission_dialog_title">시스템 알림 창 권한</string>
<string name="permission_dialog_message">이 앱은 다른 앱 위에 콘텐츠를 표시하기 위해 시스템 알림 창 권한이 필요합니다. 권한을 부여해 주세요.</string>
<string name="permission_dialog_positive_button">허용하다</string>
<string name="permission_dialog_negative_button">취소</string>
<string name="waiting_for_media">미디어를 기다리는 중</string>
</resources>

View file

@ -7,15 +7,15 @@
<string name="incompatible">A operação falhou pois é fundamentalmente incompatível com este dispositivo</string>
<string name="invalid">A operação falhou pois um ou mais dos APKs eram inválidos</string>
<string name="not_enough_storage">A operação falhou por causa de problemas de armazenamento</string>
<string name="downloading_update">Baixando a atualização...</string>
<string name="installing_update">Instalando a atualização...</string>
<string name="downloading_update">Baixando a atualização</string>
<string name="installing_update">Instalando a atualização</string>
<string name="success">Sucesso</string>
<string name="failed_to_update_with_error">Falhou em atualizar pacote com erro</string>
<string name="there_is_an_update_available_do_you_wish_to_update">Há uma atualização nova para o receptor do FCast, você quer atualizar?</string>
<string name="never">Nunca</string>
<string name="close">Fechar</string>
<string name="update">Atualizar</string>
<string name="checking_for_updates">Buscando atualizações...</string>
<string name="checking_for_updates">Buscando atualizações</string>
<string name="no_updates_available">Não há atualizações disponíveis</string>
<string name="permission_dialog_title">Sobrepor outros apps</string> <!-- Adapted to just use the translation from AOSP, Display over other apps -->
<string name="permission_dialog_message">Este app precisa da permissão para sobrepor outros apps para que possa exibir conteúdo em cima de outros apps. Por favor conceda a permissão.</string>

View file

@ -0,0 +1,25 @@
<resources>
<string name="app_name">Receptor FCast</string>
<string name="general_failure">A operação falhou de um modo genérico</string>
<string name="aborted">A operação falhou pois foi abortada ativamente</string>
<string name="blocked">A operação falhou pois foi bloqueada</string>
<string name="conflict">A operação falhou pois conflita (ou é inconsistente) com outro pacote que já está instalado no dispositivo</string>
<string name="incompatible">A operação falhou pois é fundamentalmente incompatível com este dispositivo</string>
<string name="invalid">A operação falhou pois um ou mais dos APKs eram inválidos</string>
<string name="not_enough_storage">A operação falhou por causa de problemas de armazenamento</string>
<string name="downloading_update">Baixando a atualização…</string>
<string name="installing_update">Instalando a atualização…</string>
<string name="success">Sucesso</string>
<string name="failed_to_update_with_error">Falhou em atualizar pacote com erro</string>
<string name="there_is_an_update_available_do_you_wish_to_update">Há uma atualização nova para o receptor do FCast, você quer atualizar?</string>
<string name="never">Nunca</string>
<string name="close">Fechar</string>
<string name="update">Atualizar</string>
<string name="checking_for_updates">Buscando atualizações…</string>
<string name="no_updates_available">Não há atualizações disponíveis</string>
<string name="permission_dialog_title">Sobrepor outros apps</string> <!-- Adapted to just use the translation from AOSP, Display over other apps -->
<string name="permission_dialog_message">Este app precisa da permissão para sobrepor outros apps para que possa exibir conteúdo em cima de outros apps. Por favor conceda a permissão.</string>
<string name="permission_dialog_positive_button">Permitir</string>
<string name="permission_dialog_negative_button">Cancelar</string>
<string name="waiting_for_media">Aguardando mídia</string>
</resources>

View file

@ -0,0 +1,25 @@
<resources>
<string name="app_name">FCast-приемник</string>
<string name="general_failure">Операция провалилась в общем виде</string>
<string name="aborted">Операция провалилась, потому что ее активно отменили.</string>
<string name="blocked">Операция не удалась, так как она была заблокирована</string>
<string name="conflict">Операция не выполнена, поскольку она конфликтует (или несовместима) с другим пакетом, уже установленным на устройстве.</string>
<string name="incompatible">Операция не удалась, так как она принципиально несовместима с этим устройством.</string>
<string name="invalid">Операция не удалась, так как один или несколько APK-файлов оказались недействительными.</string>
<string name="not_enough_storage">Операция не удалась из-за проблем с хранением</string>
<string name="downloading_update">Загрузка обновления…</string>
<string name="installing_update">Установка обновления…</string>
<string name="success">Успех</string>
<string name="failed_to_update_with_error">Не удалось обновить пакет из-за ошибки</string>
<string name="there_is_an_update_available_do_you_wish_to_update">Доступно обновление для приемника FCast. Хотите обновиться?</string>
<string name="never">Никогда</string>
<string name="close">Закрывать</string>
<string name="update">Обновлять</string>
<string name="checking_for_updates">Проверка обновлений…</string>
<string name="no_updates_available">Нет доступных обновлений</string>
<string name="permission_dialog_title">Разрешение окна системного оповещения</string>
<string name="permission_dialog_message">Этому приложению требуется разрешение System Alert Window для отображения контента поверх других приложений. Пожалуйста, предоставьте разрешение.</string>
<string name="permission_dialog_positive_button">Позволять</string>
<string name="permission_dialog_negative_button">Отмена</string>
<string name="waiting_for_media">Ожидание СМИ</string>
</resources>

View file

@ -0,0 +1,25 @@
<resources>
<string name="app_name">FCast接收器</string>
<string name="general_failure">操作以一般方式失败</string>
<string name="aborted">操作失败,因为它被主动中止</string>
<string name="blocked">操作失败,因为被阻止</string>
<string name="conflict">操作失败,因为它与设备上已安装的另一个软件包冲突(或不一致)</string>
<string name="incompatible">操作失败,因为它与该设备根本不兼容</string>
<string name="invalid">操作失败,因为一个或多个 APK 无效</string>
<string name="not_enough_storage">由于存储问题,操作失败</string>
<string name="downloading_update">正在下载更新…</string>
<string name="installing_update">正在安装更新…</string>
<string name="success">成功</string>
<string name="failed_to_update_with_error">更新包失败并出现错误</string>
<string name="there_is_an_update_available_do_you_wish_to_update">FCast 接收器有可用的更新,您是否要更新?</string>
<string name="never">绝不</string>
<string name="close">关闭</string>
<string name="update">更新</string>
<string name="checking_for_updates">正在检查更新…</string>
<string name="no_updates_available">没有可用更新</string>
<string name="permission_dialog_title">系统警报窗口权限</string>
<string name="permission_dialog_message">此应用需要“系统警报窗口”权限才能在其他应用上方显示内容。请授予此权限。</string>
<string name="permission_dialog_positive_button">允许</string>
<string name="permission_dialog_negative_button">取消</string>
<string name="waiting_for_media">等待媒体</string>
</resources>

View file

@ -7,15 +7,15 @@
<string name="incompatible">The operation failed because it is fundamentally incompatible with this device</string>
<string name="invalid">The operation failed because one or more of the APKs was invalid</string>
<string name="not_enough_storage">The operation failed because of storage issues</string>
<string name="downloading_update">Downloading update...</string>
<string name="installing_update">Installing update...</string>
<string name="downloading_update">Downloading update</string>
<string name="installing_update">Installing update</string>
<string name="success">Success</string>
<string name="failed_to_update_with_error">Failed to update package with error</string>
<string name="there_is_an_update_available_do_you_wish_to_update">There is an update available for FCast receiver, do you wish to update?</string>
<string name="never">Never</string>
<string name="close">Close</string>
<string name="update">Update</string>
<string name="checking_for_updates">Checking for updates...</string>
<string name="checking_for_updates">Checking for updates</string>
<string name="no_updates_available">No updates available</string>
<string name="permission_dialog_title">System Alert Window Permission</string>
<string name="permission_dialog_message">This app requires the System Alert Window permission to display content on top of other apps. Please grant the permission.</string>

View file

@ -1,6 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'com.android.application' version '8.2.0' apply false
id 'com.android.library' version '8.2.0' apply false
id 'org.jetbrains.kotlin.android' version '1.8.0' apply false
id 'com.android.application' version '8.11.0' apply false
id 'com.android.library' version '8.11.0' apply false
id 'org.jetbrains.kotlin.android' version '2.2.0' apply false
}

View file

@ -1,32 +0,0 @@
#!/bin/sh
if [ -z "$ANDROID_VERSION_NAME" ] || [ -z "$ANDROID_VERSION_CODE" ]; then echo "Version name or code not specified. Skipping build."; exit 0; fi
DOCUMENT_ROOT=/var/www/html
# Build content
echo "Building content..."
./gradlew --stacktrace assembleRelease -PversionName=$ANDROID_VERSION_NAME -PversionCode=$ANDROID_VERSION_CODE
./gradlew --stacktrace bundlePlaystoreRelease -PversionName=$ANDROID_VERSION_NAME -PversionCode=$ANDROID_VERSION_CODE
# Take site offline
echo "Taking site offline..."
touch $DOCUMENT_ROOT/maintenance.file
# Swap over the content
echo "Deploying content..."
echo $ANDROID_VERSION_CODE > /var/www/html/fcast-version.txt
cp ./app/build/outputs/apk/defaultFlavor/release/app-defaultFlavor-release.apk /var/www/html/fcast-release.apk
cp ./app/build/outputs/bundle/playstoreRelease/app-playstore-release.aab /var/www/html/fcast-playstore-release.aab
# Notify Cloudflare to wipe the CDN cache
echo "Purging Cloudflare cache..."
curl -X POST "https://api.cloudflare.com/client/v4/zones/ff904f7348b9513064b23e852e328abb/purge_cache" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{"purge_everything":true}'
sleep 30
# Take site back online
echo "Bringing site back online..."
rm $DOCUMENT_ROOT/maintenance.file

View file

@ -1,6 +1,6 @@
#Mon May 01 06:24:43 CDT 2023
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME