397 lines
14 KiB
Kotlin
397 lines
14 KiB
Kotlin
/**
|
|
* Copyright (C) 2021 Anthony Chomienne
|
|
* This program is free software: you can redistribute it and/or modify it under the terms of the
|
|
* GNU Affero General Public License as published by the Free Software Foundation, version 3.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
* See the GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License along with this program.
|
|
* If not, see <https://www.gnu.org/licenses/>
|
|
*/
|
|
|
|
package fr.mobdev.peertubelive.activity
|
|
|
|
import android.Manifest.permission
|
|
import android.content.*
|
|
import android.content.pm.PackageManager
|
|
import android.hardware.SensorManager
|
|
import android.net.Uri
|
|
import android.os.Build
|
|
import android.os.Bundle
|
|
import android.provider.Settings
|
|
import android.view.OrientationEventListener
|
|
import android.view.SurfaceHolder
|
|
import android.view.View
|
|
import android.view.WindowManager
|
|
import android.widget.Toast
|
|
import androidx.appcompat.app.AlertDialog
|
|
import androidx.appcompat.app.AppCompatActivity
|
|
import androidx.core.app.ActivityCompat
|
|
import androidx.core.content.ContextCompat
|
|
import androidx.databinding.DataBindingUtil
|
|
import com.pedro.encoder.input.video.CameraHelper
|
|
import com.pedro.rtmp.utils.ConnectCheckerRtmp
|
|
import com.pedro.rtplibrary.rtmp.RtmpCamera2
|
|
import fr.mobdev.peertubelive.R
|
|
import fr.mobdev.peertubelive.databinding.StreamBinding
|
|
import fr.mobdev.peertubelive.manager.InstanceManager.EXTRA_DATA
|
|
import fr.mobdev.peertubelive.objects.StreamData
|
|
import java.util.*
|
|
import kotlin.collections.ArrayList
|
|
|
|
class StreamActivity : AppCompatActivity() {
|
|
|
|
|
|
private lateinit var binding: StreamBinding
|
|
private lateinit var rtmpCamera2 : RtmpCamera2
|
|
private lateinit var streamData: StreamData
|
|
private lateinit var orientationEventListener: OrientationEventListener
|
|
private lateinit var lockReceiver: BroadcastReceiver
|
|
private var surfaceInit: Boolean = false
|
|
private var permissionGiven: Boolean = false
|
|
private var streamIsActive: Boolean = false
|
|
private var screenOrientation: Int = -1
|
|
private var lastScreenOrientation: Int = 0
|
|
private var rotationIsEnabled: Boolean = true
|
|
|
|
private var orientationTimer: Timer = Timer()
|
|
|
|
companion object {
|
|
const val BACKGROUND :Int = 1
|
|
const val LOCK :Int = 2
|
|
const val BACK :Int = 3
|
|
const val STOP :Int = 4
|
|
const val NETWORK_ERROR :Int = 5
|
|
|
|
}
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
super.onCreate(savedInstanceState)
|
|
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
|
binding = DataBindingUtil.setContentView(this, R.layout.stream)
|
|
|
|
|
|
|
|
orientationEventListener = object: OrientationEventListener(this, SensorManager.SENSOR_DELAY_NORMAL){
|
|
override fun onOrientationChanged(orientation: Int) {
|
|
handlerOrientation(orientation)
|
|
}
|
|
}
|
|
orientationEventListener.enable()
|
|
|
|
streamData = intent.getParcelableExtra<StreamData>(EXTRA_DATA)!!
|
|
|
|
binding.stop.setOnClickListener {
|
|
askStopLive(STOP)
|
|
}
|
|
binding.switchCamera.setOnClickListener { rtmpCamera2.switchCamera() }
|
|
binding.switchCamera.visibility = View.GONE
|
|
binding.muteMicro.setOnClickListener {
|
|
if (rtmpCamera2.isAudioMuted) {
|
|
rtmpCamera2.enableAudio()
|
|
binding.muteMicro.setImageResource(R.drawable.baseline_volume_up_24)
|
|
}
|
|
else {
|
|
rtmpCamera2.disableAudio()
|
|
binding.muteMicro.setImageResource(R.drawable.baseline_volume_off_24)
|
|
}
|
|
}
|
|
binding.muteMicro.visibility = View.GONE
|
|
binding.flash.setOnClickListener {
|
|
if (rtmpCamera2.isLanternEnabled) {
|
|
rtmpCamera2.disableLantern()
|
|
binding.flash.setImageResource(R.drawable.baseline_flash_off_24)
|
|
}
|
|
else {
|
|
rtmpCamera2.enableLantern()
|
|
binding.flash.setImageResource(R.drawable.baseline_flash_on_24)
|
|
}
|
|
}
|
|
binding.flash.visibility = View.GONE
|
|
|
|
binding.rotation.setOnClickListener {
|
|
if (rotationIsEnabled)
|
|
binding.rotation.setImageResource(R.drawable.baseline_screen_lock_rotation_24)
|
|
else
|
|
binding.rotation.setImageResource(R.drawable.baseline_screen_rotation_24)
|
|
rotationIsEnabled = !rotationIsEnabled
|
|
}
|
|
binding.rotation.visibility = View.GONE
|
|
binding.surfaceView.holder.addCallback(object: SurfaceHolder.Callback {
|
|
override fun surfaceCreated(p0: SurfaceHolder) {
|
|
surfaceInit = true
|
|
if (permissionGiven)
|
|
startStream()
|
|
}
|
|
|
|
override fun surfaceChanged(p0: SurfaceHolder, p1: Int, p2: Int, p3: Int) {
|
|
|
|
}
|
|
|
|
override fun surfaceDestroyed(p0: SurfaceHolder) {
|
|
if (this@StreamActivity::rtmpCamera2.isInitialized) {
|
|
if (rtmpCamera2.isStreaming) {
|
|
rtmpCamera2.stopStream()
|
|
}
|
|
rtmpCamera2.stopPreview()
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
)
|
|
|
|
lockReceiver = object : BroadcastReceiver() {
|
|
override fun onReceive(context: Context?, intent: Intent?) {
|
|
if (intent?.action.equals(Intent.ACTION_SCREEN_OFF)){
|
|
setResult(LOCK)
|
|
finish()
|
|
} else if (intent?.action.equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)){
|
|
val reason = intent?.getStringExtra("reason")
|
|
if(reason.equals("homekey")){
|
|
setResult(BACKGROUND)
|
|
finish()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
val filter = IntentFilter(Intent.ACTION_SCREEN_OFF)
|
|
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)
|
|
registerReceiver(lockReceiver, filter)
|
|
}
|
|
|
|
override fun onResume() {
|
|
super.onResume()
|
|
val permissions = getUnAllowedPermissions()
|
|
|
|
if(permissions.isNotEmpty()) {
|
|
var shouldShowRequest = true
|
|
for(perm in permissions){
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
|
|
shouldShowRequest = shouldShowRequest && shouldShowRequestPermissionRationale(perm)
|
|
}
|
|
|
|
if(shouldShowRequest) {
|
|
binding.permissionInfo.visibility = View.VISIBLE
|
|
binding.gotoPermission.visibility = View.VISIBLE
|
|
binding.surfaceView.visibility = View.GONE
|
|
|
|
binding.gotoPermission.setOnClickListener {
|
|
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
|
|
intent.data = Uri.fromParts("package", packageName, null)
|
|
startActivity(intent)
|
|
}
|
|
} else {
|
|
ActivityCompat.requestPermissions(this, permissions.toTypedArray(), 1)
|
|
}
|
|
} else {
|
|
binding.surfaceView.visibility = View.VISIBLE
|
|
permissionGiven = true
|
|
if(surfaceInit && !streamIsActive)
|
|
startStream()
|
|
}
|
|
}
|
|
|
|
override fun onStop() {
|
|
super.onStop()
|
|
if (this@StreamActivity::rtmpCamera2.isInitialized && !hasWindowFocus()) {
|
|
unregisterReceiver(lockReceiver)
|
|
setResult(BACKGROUND)
|
|
finish()
|
|
}
|
|
}
|
|
|
|
override fun onBackPressed() {
|
|
askStopLive(BACK)
|
|
}
|
|
|
|
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
|
var allPermissionGranted = true
|
|
for (result in grantResults) {
|
|
allPermissionGranted = allPermissionGranted && (result == PackageManager.PERMISSION_GRANTED)
|
|
}
|
|
|
|
if (allPermissionGranted) {
|
|
permissionGiven = true
|
|
if(surfaceInit && !streamIsActive)
|
|
startStream()
|
|
} else {
|
|
binding.permissionInfo.visibility = View.VISIBLE
|
|
binding.gotoPermission.visibility = View.VISIBLE
|
|
binding.surfaceView.visibility = View.GONE
|
|
}
|
|
}
|
|
|
|
private fun handlerOrientation(orientation: Int) {
|
|
if(orientation < 0 || !rotationIsEnabled) {
|
|
return
|
|
}
|
|
var localOrientation: Int
|
|
localOrientation = when (orientation) {
|
|
in 45..135 -> {
|
|
90
|
|
}
|
|
in 135..225 -> {
|
|
180
|
|
}
|
|
in 225..315 -> {
|
|
270
|
|
}
|
|
else -> {
|
|
0
|
|
}
|
|
}
|
|
|
|
if(localOrientation != lastScreenOrientation) {
|
|
lastScreenOrientation = localOrientation
|
|
orientationTimer.cancel()
|
|
orientationTimer.purge()
|
|
orientationTimer = Timer()
|
|
orientationTimer.schedule(object : TimerTask() {
|
|
override fun run() {
|
|
if (lastScreenOrientation != screenOrientation) {
|
|
screenOrientation = lastScreenOrientation
|
|
rtmpCamera2.glInterface.setStreamRotation(screenOrientation)
|
|
|
|
if (screenOrientation == 90) {
|
|
localOrientation = 270
|
|
} else if(screenOrientation == 270) {
|
|
localOrientation = 90
|
|
}
|
|
binding.flash.rotation = localOrientation.toFloat()
|
|
binding.muteMicro.rotation = localOrientation.toFloat()
|
|
binding.switchCamera.rotation = localOrientation.toFloat()
|
|
binding.rotation.rotation = localOrientation.toFloat()
|
|
}
|
|
}
|
|
},3000)
|
|
}
|
|
}
|
|
|
|
private fun startStream() {
|
|
streamIsActive = true
|
|
binding.permissionInfo.visibility = View.GONE
|
|
binding.gotoPermission.visibility = View.GONE
|
|
binding.surfaceView.visibility = View.VISIBLE
|
|
binding.muteMicro.visibility = View.VISIBLE
|
|
binding.rotation.visibility = View.VISIBLE
|
|
binding.switchCamera.visibility = View.VISIBLE
|
|
binding.flash.visibility = View.VISIBLE
|
|
|
|
val connectChecker : ConnectCheckerRtmp = object : ConnectCheckerRtmp {
|
|
override fun onConnectionStartedRtmp(rtmpUrl: String) {
|
|
|
|
}
|
|
|
|
override fun onConnectionSuccessRtmp() {
|
|
runOnUiThread {
|
|
Toast.makeText(binding.root.context, "Connection success", Toast.LENGTH_SHORT).show();
|
|
|
|
}
|
|
}
|
|
|
|
override fun onConnectionFailedRtmp(reason: String) {
|
|
runOnUiThread {
|
|
Toast.makeText(binding.root.context, "Connection failed", Toast.LENGTH_SHORT).show();
|
|
rtmpCamera2.stopStream()
|
|
setResult(NETWORK_ERROR)
|
|
finish()
|
|
}
|
|
}
|
|
|
|
override fun onNewBitrateRtmp(bitrate: Long) {
|
|
|
|
}
|
|
|
|
override fun onDisconnectRtmp() {
|
|
runOnUiThread {
|
|
Toast.makeText(binding.root.context, "Disconnect", Toast.LENGTH_SHORT).show();
|
|
}
|
|
|
|
}
|
|
|
|
override fun onAuthErrorRtmp() {
|
|
runOnUiThread {
|
|
Toast.makeText(binding.root.context, "Auth Error", Toast.LENGTH_SHORT).show();
|
|
}
|
|
|
|
}
|
|
|
|
override fun onAuthSuccessRtmp() {
|
|
runOnUiThread {
|
|
Toast.makeText(binding.root.context, "Auth Success", Toast.LENGTH_SHORT).show();
|
|
}
|
|
}
|
|
}
|
|
|
|
rtmpCamera2 = RtmpCamera2(binding.surfaceView, connectChecker)
|
|
|
|
var width = 1920
|
|
var height = 1080
|
|
when (streamData.resolution) {
|
|
StreamData.STREAM_RESOLUTION.p2160 -> {
|
|
width = 3840
|
|
height = 2160
|
|
}
|
|
StreamData.STREAM_RESOLUTION.p1440 -> {
|
|
width = 2560
|
|
height = 1440
|
|
}
|
|
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
|
|
}
|
|
}
|
|
|
|
rtmpCamera2.startPreview(CameraHelper.Facing.BACK, width,height)
|
|
|
|
//start stream
|
|
|
|
if (rtmpCamera2.prepareAudio() && rtmpCamera2.prepareVideo(width,height,30, (width*height*30*0.076).toInt(),CameraHelper.getCameraOrientation(this),true)) {
|
|
println("peertubeurl "+streamData.url+"/"+streamData.key)
|
|
rtmpCamera2.startStream(streamData.url+"/"+streamData.key)
|
|
} else {
|
|
/**This device cant init encoders, this could be for 2 reasons: The encoder selected doesnt support any configuration setted or your device hasnt a H264 or AAC encoder (in this case you can see log error valid encoder not found) */
|
|
}
|
|
}
|
|
|
|
private fun getUnAllowedPermissions(): List<String> {
|
|
val permissions: ArrayList<String> = ArrayList<String>()
|
|
|
|
if (ContextCompat.checkSelfPermission(this, permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
|
|
permissions.add(permission.CAMERA)
|
|
}
|
|
if (ContextCompat.checkSelfPermission(this, permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
|
|
permissions.add(permission.RECORD_AUDIO)
|
|
}
|
|
return permissions
|
|
}
|
|
|
|
private fun askStopLive(reason: Int) {
|
|
val alertBuilder = AlertDialog.Builder(this)
|
|
alertBuilder.setTitle(R.string.end_stream)
|
|
alertBuilder.setMessage(R.string.ask_end_stream)
|
|
alertBuilder.setPositiveButton(R.string.yes) { _: DialogInterface, _: Int ->
|
|
setResult(reason)
|
|
finish()
|
|
}
|
|
alertBuilder.setNegativeButton(R.string.no,null)
|
|
alertBuilder.show()
|
|
}
|
|
|
|
} |