-
-
Notifications
You must be signed in to change notification settings - Fork 879
Open
Description
Library : implementation("com.github.pedroSG94.RootEncoder:library:2.6.6")
Android Manifest :
<uses-permission
android:name="android.permission.FOREGROUND_SERVICE"
tools:ignore="ForegroundServicesPolicy" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
<service
android:name=".services.ShareScreenService"
android:exported="false"
android:foregroundServiceType="mediaProjection|microphone|camera" />
-
Caused by android.app.ForegroundServiceStartNotAllowedException: Service.startForeground() not allowed due to mAllowStartForeground false: service
-
Fatal Exception: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.os.Looper.quit()' on a null object reference
at com.pedro.encoder.BaseEncoder.stop(BaseEncoder.java:181)
at com.pedro.encoder.BaseEncoder.stop(BaseEncoder.java:167)
at com.pedro.library.base.StreamBase.stopSources(StreamBase.kt:526)
at com.pedro.library.base.StreamBase.stopStream(StreamBase.kt:251)
at com.atharvasystem.quickscreenrecorder.services.ShareScreenService$stopStream$1$1.invokeSuspend(ShareScreenService.kt:148)
- Fatal Exception: java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=1863891168, result=-1, data=Intent { (has extras) }} to activity {com.atharvasystem.quickscreenrecorder/com.atharvasystem.quickscreenrecorder.ui.streams.StreamSourceActivity}: java.lang.SecurityException: Media projections require a foreground service of type ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION
class ShareScreenService : Service(), ConnectChecker {
companion object {
private const val TAG = "ShareScreenService"
private const val channelId = "rtpDisplayStreamChannel"
private const val defaultAudioSource = 0 // 0 = MicroPhone, 1 = Internal, 2 = Mix Audio
const val notifyId = 123456
var INSTANCE: ShareScreenService? = null
}
private lateinit var notificationManager: NotificationManager
private lateinit var genericStream: GenericStream
private val mediaProjectionManager: MediaProjectionManager by lazy {
applicationContext.getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
}
private var mediaProjection: MediaProjection? = null
private var callback: ConnectChecker? = null
private var width = 1920
private var height = 1080
private var vBitrate = 4000 * 1000
private var rotation = 0 //0 for landscape or 90 for portrait
private var framePerSecond = 15
private var sampleRate = 32000
private var isStereo = true
private var aBitrate = 128 * 1000
private var prepared = false
private var selectedAudioSource: Int = defaultAudioSource
private val isForegroundStarted = AtomicBoolean(false)
override fun onCreate() {
super.onCreate()
notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
channelId,
channelId, // Add this string resource
NotificationManager.IMPORTANCE_LOW
)
notificationManager.createNotificationChannel(channel)
}
genericStream =
GenericStream(baseContext, this, NoVideoSource(), MicrophoneSource()).apply {
//This is important to keep a constant fps because media projection only produce fps if the screen change
getGlInterface().setForceRender(true, framePerSecond)
}
INSTANCE = this
}
private fun keepAliveTrick() {
if (isForegroundStarted.get()) return
val notification = NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.drawable.ic_notification)
.setSilent(true)
.setOngoing(true)
.setColor(Color.BLACK)
.setAutoCancel(false)
.setContentTitle(getString(R.string.live_broadcast_progress))
.setPriority(NotificationCompat.PRIORITY_LOW)
.build()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
val serviceTypes = ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION
startForeground(
notifyId,
notification,
serviceTypes
)
} else {
startForeground(notifyId, notification)
}
isForegroundStarted.set(true)
}
override fun onBind(p0: Intent?): IBinder? {
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
return START_STICKY
}
fun sendIntent(): Intent {
return mediaProjectionManager.createScreenCaptureIntent()
}
fun isStreaming(): Boolean {
return genericStream.isStreaming
}
fun getGenericStream(): GenericStream {
return genericStream
}
fun isRecording(): Boolean {
return genericStream.isRecording
}
fun startRecording(outputPath: String) {
genericStream.startRecord(outputPath) {
}
}
fun stopRecording(): Boolean {
return genericStream.stopRecord()
}
fun stopStreamingService(context: Context) {
val intent = Intent(context, ShareScreenService::class.java)
context.stopService(intent)
}
fun stopStream() {
try {
if (genericStream.isStreaming || genericStream.isRecording) {
if (genericStream.isRecording) {
stopRecording()
}
if (genericStream.isStreaming) {
GlobalScope.launch(Dispatchers.Main) { // Or a custom scope
withContext(Dispatchers.IO) { // Perform blocking operation on IO dispatcher
genericStream.stopStream()
}
}
}
notificationManager.cancel(notifyId)
stopForeground(true) // Add this to remove foreground state
stopSelf() // Uncomment this to stop the service
}
} catch (e: Exception) {
e.printStackTrace()
try {
stopRecording()
stopStream()
} catch (e: Exception) {
e.printStackTrace()
}
stopSelf()
}
}
fun setCallback(connectChecker: ConnectChecker?) {
callback = connectChecker
}
override fun onDestroy() {
super.onDestroy()
stopStream()
INSTANCE = null
}
fun prepareStream(resultCode: Int, data: Intent): Boolean {
keepAliveTrick()
stopStream()
val mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data)
if (mediaProjection == null) {
Log.e(TAG, "MediaProjection is null")
return false
}
this.mediaProjection = mediaProjection
val screenSource = ScreenSource(applicationContext, mediaProjection)
return try {
//ScreenSource need use always setCameraOrientation(0) because the MediaProjection handle orientation.
//You also need remove autoHandleOrientation if you are using it.
//You need to call it after prepareVideo to override the default value.
screenSource.let {
genericStream.let {
genericStream.getGlInterface().let {
genericStream.getGlInterface().setCameraOrientation(0)
}
genericStream.changeVideoSource(screenSource)
genericStream.getStreamClient().setReTries(5)
prepareVideoAudio()
}
}
} catch (ignored: IllegalArgumentException) {
false
}
}
fun startStream(endpoint: String) {
if (!genericStream.isStreaming) {
genericStream.startStream(endpoint)
}
}
override fun onConnectionStarted(url: String) {
callback?.onConnectionStarted(url)
}
override fun onConnectionSuccess() {
callback?.onConnectionSuccess()
}
override fun onNewBitrate(bitrate: Long) {
callback?.onNewBitrate(bitrate)
if (SharePrefUtil.isAdaptiveBitrateEnabled(this)) {
genericStream.setVideoBitrateOnFly(bitrate.toInt())
}
}
override fun onConnectionFailed(reason: String) {
callback?.onConnectionFailed(reason)
}
override fun onDisconnect() {
callback?.onDisconnect()
}
override fun onAuthError() {
callback?.onAuthError()
}
override fun onAuthSuccess() {
callback?.onAuthSuccess()
}
private fun prepareVideoAudio(): Boolean {
if (genericStream.isStreaming || genericStream.isRecording || genericStream.isOnPreview) {
genericStream.stopStream()
}
if (SharePrefUtil.isPurchased(this)) {
val width1 = SharePrefUtil.getResolution(this, getString(R.string.broadcastScreen))
val height1 = ViewUtil.getResolutionHeight(
SharePrefUtil.getResolution(
this, getString(R.string.broadcastScreen)
)
)
width = height1
height = width1
vBitrate = ViewUtil.getVideoBitrate(1080)
} else {
val width1 = SharePrefUtil.getResolution(this, getString(R.string.broadcastScreen))
val height1 = ViewUtil.getResolutionHeight(
SharePrefUtil.getResolution(
this, getString(R.string.broadcastScreen)
)
)
width = height1
height = width1
vBitrate = ViewUtil.getVideoBitrate(720)
}
prepared = try {
genericStream.prepareVideo(
width = width,
height = height,
bitrate = vBitrate,
rotation = rotation,
fps = SharePrefUtil.getFrameRate(this, getString(R.string.broadcastScreen)),
) && genericStream.prepareAudio(
sampleRate, isStereo, aBitrate, echoCanceler = true, noiseSuppressor = true
)
} catch (e: IllegalArgumentException) {
false
}
return prepared
}
fun getCurrentAudioSource(): AudioSource = genericStream.audioSource
fun toggleAudioSource(itemId: Int) {
when (itemId) {
0 -> {
selectedAudioSource = 0
if (genericStream.audioSource is MicrophoneSource) return
genericStream.changeAudioSource(MicrophoneSource())
}
1-> {
selectedAudioSource = 1
if (genericStream.audioSource is InternalAudioSource) return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
mediaProjection?.let { genericStream.changeAudioSource(InternalAudioSource(it)) }
} else {
throw IllegalArgumentException("You need min API 29+")
}
}
2 -> {
selectedAudioSource = 2
if (genericStream.audioSource is MixAudioSource) return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
mediaProjection?.let { genericStream.changeAudioSource(MixAudioSource(it)) }
} else {
throw IllegalArgumentException("You need min API 29+")
}
}
}
}
}
Metadata
Metadata
Assignees
Labels
No labels