| | |
| | | |
| | | import android.content.Context |
| | | import android.content.res.Configuration |
| | | import android.graphics.PixelFormat |
| | | import android.graphics.Point |
| | | import android.graphics.PointF |
| | | import android.hardware.usb.UsbDevice |
| | |
| | | import androidx.compose.ui.window.Dialog |
| | | import androidx.constraintlayout.compose.ConstraintLayout |
| | | import androidx.lifecycle.viewModelScope |
| | | import com.runt.live.R |
| | | import com.runt.live.cpp.LiveMiniView |
| | | import com.runt.live.data.LiveAddress |
| | | import com.runt.live.data.LiveStreams |
| | |
| | | |
| | | var liveAdressesState = mutableStateListOf<LiveAddress>() |
| | | |
| | | var subStreamsState = mutableStateOf(listOf<StreamWindow>()) |
| | | var liveStreamsState = mutableStateOf(LiveStreams(subStreamsState)) |
| | | |
| | | var showMainBorder = mutableStateOf(false); |
| | | var mainStreamState = mutableStateOf(0) |
| | |
| | | |
| | | //新窗口 |
| | | var openSurfaceClick = MutableSharedFlow<StreamWindow>(); |
| | | |
| | | fun pxToDp(pix:Int):Dp{ |
| | | var dpV = pix/mViewModel.getActivity().resources.displayMetrics.density; |
| | | return dpV.dp; |
| | | } |
| | | |
| | | fun sp2px(spValue: Float): Int { |
| | | val fontScale = mViewModel.getActivity().resources.displayMetrics.density |
| | | return (spValue * fontScale + 0.5f).toInt() |
| | | } |
| | | |
| | | |
| | | companion object { |
| | | var uvcList = mutableStateOf(listOf<UsbDevice>()) |
| | | var usbDevices = mutableStateOf(listOf<UsbDevice>()) |
| | | var subStreamsState = mutableStateOf(listOf<StreamWindow>()) |
| | | var liveStreamsState = mutableStateOf(LiveStreams(subStreamsState)) |
| | | } |
| | | |
| | | fun onCloseClick(streamWindow : StreamWindow){ |
| | |
| | | streamWindow.surfaceView = this; |
| | | streamWindow.surfaceHolder = holder; |
| | | streamWindow.listener?.onSurfaceUpdate?.let { it(holder.surface) } |
| | | if(streamWindow.streamType == StreamType.TEXT){ |
| | | holder.setFormat(PixelFormat.TRANSLUCENT) // 透明像素格式 |
| | | setBackgroundColor(context.resources.getColor(R.color.transparent)) // 设置背景透明 |
| | | } |
| | | holder.addCallback(streamWindow.surfaceCallBack) |
| | | } |
| | | |
| | |
| | | streamWindow.surfaceView?.setZOrderMediaOverlay(true) |
| | | subStreamsState.value = value; |
| | | for (stream in subStreamsState.value) { |
| | | mViewModel.updateMiniView(subStreamsState.value.indexOf(streamWindow) , streamWindow) |
| | | mViewModel.updateMiniView(subStreamsState.value.indexOf(streamWindow), streamWindow) |
| | | } |
| | | } |
| | | .align(alignment = Alignment.TopStart), |
| | |
| | | } |
| | | } |
| | | } |
| | | val openInputDialog = remember { mutableStateOf(false) } |
| | | val rtmpInputDialog = remember { mutableStateOf(false) } |
| | | val textInputDialog = remember { mutableStateOf(false) } |
| | | val openScreenDialog = remember { mutableStateOf(false) } |
| | | Row { |
| | | if(!liveStreamsState.value.hasCamera()){ |
| | |
| | | } |
| | | } |
| | | Button(onClick = { |
| | | openInputDialog.value = true |
| | | rtmpInputDialog.value = true |
| | | }, modifier = Modifier.padding(start = 10.dp), contentPadding = PaddingValues(0.dp)) { |
| | | Text(text = "RTMP") |
| | | } |
| | |
| | | Text(text = "音频") |
| | | }*/ |
| | | // USB Video Class (UVC) 视频采集卡 |
| | | if(uvcList.value.size > 0){ |
| | | if(usbDevices.value.size > 0){ |
| | | Button(onClick = { |
| | | var streamWindow = StreamWindow(streamType = StreamType.UVC); |
| | | //默认没有音频 |
| | | streamWindow.hasAudioState.value = false; |
| | | streamWindow.remark = uvcList.value[0].deviceName |
| | | streamWindow.remark = usbDevices.value[0].deviceName |
| | | newSurfaceClick(streamWindow) |
| | | }, modifier = Modifier.padding(start = 10.dp), contentPadding = PaddingValues(0.dp)) { |
| | | Text(text = "UVC") |
| | | } |
| | | } |
| | | Button(onClick = { |
| | | textInputDialog.value = true |
| | | }, modifier = Modifier.padding(start = 10.dp), contentPadding = PaddingValues(0.dp)) { |
| | | Text(text = "文本") |
| | | } |
| | | if(!liveStreamsState.value.hasSystem()) { |
| | | Button(onClick = { |
| | |
| | | } |
| | | } |
| | | when{ |
| | | openInputDialog.value->{ |
| | | InputDialog(onDismissRequest = { openInputDialog.value = false } , onConfirmRequest = { |
| | | openInputDialog.value = false |
| | | rtmpInputDialog.value->{ |
| | | var url = (mViewModel.getActivity() as LiveActivity ).getStringProjectPrefrence("pullAddr","rtmp://192.168.28.79/live"); |
| | | InputDialog(url,StreamType.RTMP,onDismissRequest = { rtmpInputDialog.value = false } , onConfirmRequest = { |
| | | rtmpInputDialog.value = false |
| | | (mViewModel.getActivity() as LiveActivity ) .putStringProjectPrefrence("pullAddr",it) |
| | | var streamWindow = StreamWindow(streamType = StreamType.RTMP, remark = it); |
| | | streamWindow.hasVideoState.value = false; |
| | | streamWindow.hasAudioState.value = false; |
| | | newSurfaceClick(streamWindow) |
| | | }) |
| | | } |
| | | textInputDialog.value->{ |
| | | var url = (mViewModel.getActivity() as LiveActivity ).getStringProjectPrefrence("live_text",""); |
| | | InputDialog(url,StreamType.TEXT,onDismissRequest = { textInputDialog.value = false } , onConfirmRequest = { |
| | | textInputDialog.value = false |
| | | (mViewModel.getActivity() as LiveActivity ) .putStringProjectPrefrence("live_text",it) |
| | | var streamWindow = StreamWindow(streamType = StreamType.TEXT, remark = it); |
| | | streamWindow.hasVideoState.value = true; |
| | | streamWindow.hasAudioState.value = false; |
| | | newSurfaceClick(streamWindow) |
| | | }) |
| | |
| | | } |
| | | |
| | | @Composable |
| | | fun InputDialog(onDismissRequest:()->Unit,onConfirmRequest:(String)->Unit){ |
| | | var url = (mViewModel.getActivity() as LiveActivity ).getStringProjectPrefrence("pullAddr","rtmp://192.168.28.79/live"); |
| | | fun InputDialog(url:String,streamType : StreamType,onDismissRequest:()->Unit,onConfirmRequest:(String)->Unit){ |
| | | var text by remember { |
| | | mutableStateOf(url) |
| | | } |
| | | var modifier : Modifier; |
| | | var keyboardType : KeyboardType; |
| | | if(streamType == StreamType.RTMP){ |
| | | modifier = Modifier |
| | | .width(260.dp) |
| | | .height(50.dp) |
| | | .padding(0.dp) |
| | | keyboardType = KeyboardType.Uri; |
| | | }else{ |
| | | modifier = Modifier |
| | | .width(260.dp) |
| | | .padding(0.dp) |
| | | keyboardType = KeyboardType.Text; |
| | | } |
| | | AlertDialog(onDismissRequest = { onDismissRequest() } , |
| | | confirmButton = { |
| | | TextButton(onClick = { |
| | | val pattern = """rtmp://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]""".toRegex() |
| | | if(text.length > 7 && pattern.matches(text)){ |
| | | onConfirmRequest(text) |
| | | if(streamType == StreamType.RTMP){ |
| | | val pattern = """rtmp://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]""".toRegex() |
| | | if(text.length > 7 && pattern.matches(text)){ |
| | | onConfirmRequest(text) |
| | | }else{ |
| | | Toast.makeText(mViewModel.getActivity(),"地址格式错误",Toast.LENGTH_SHORT).show(); |
| | | } |
| | | }else{ |
| | | Toast.makeText(mViewModel.getActivity(),"地址格式错误",Toast.LENGTH_SHORT).show(); |
| | | onConfirmRequest(text) |
| | | } |
| | | }) { |
| | | Text(text = "确认") |
| | |
| | | } |
| | | }, |
| | | title = { |
| | | Text(text = "请输入直播地址", fontSize = 16.sp) |
| | | if(streamType == StreamType.RTMP) { |
| | | Text(text = "请输入直播地址" , fontSize = 16.sp) |
| | | }else{ |
| | | Text(text = "请输入要展示的文本" , fontSize = 16.sp) |
| | | } |
| | | }, |
| | | text = { |
| | | TextField(value = text , onValueChange = { |
| | | //val pattern = """(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]""".toRegex() |
| | | text = it; |
| | | },singleLine = true, |
| | | modifier = Modifier |
| | | .width(260.dp) |
| | | .height(50.dp) |
| | | .padding(0.dp), |
| | | },singleLine = streamType == StreamType.RTMP, |
| | | modifier = modifier, |
| | | keyboardOptions = KeyboardOptions( |
| | | keyboardType = KeyboardType.Uri |
| | | keyboardType = keyboardType |
| | | ) , |
| | | placeholder = { |
| | | Text(text = "请输入直播地址", fontSize = 14.sp) |
| | | if(streamType == StreamType.RTMP) { |
| | | Text(text = "请输入直播地址" , fontSize = 14.sp) |
| | | }else{ |
| | | Text(text = "请输入要展示的文本" , fontSize = 14.sp) |
| | | } |
| | | } |
| | | , textStyle = TextStyle(fontSize = 14.sp, color = Color.Black)) |
| | | }) |