עזרה | כתיבת קוד קוטלין עבור ספריית RootEncoder
-
היי שלום
אני מחפש מפתח קוטלין שיודע לעבוד עם ספריית RootEncoder (ספריית סטרימינג) שרת OME ורכיב מצלמה
דרושים לי הדברים הבאים:
1 כתיבת ה layout של המסך עם רכיבי המצלמה (כפתורים תצוגה קדימה וכד׳)
2 כתיבת קוד קוטלין שמטפל בכל הרכיבים במסך ומזרים את התשדיר לשרת OME
יש לי כרגע קוד של המסך אבל אני לא מצליח להבין למה הוא מורח את הצילום על כל המסך ולמה כשאני מסובב את המכשיר התמונה מתעוותת
אני משלם 200₪ על התוצאה שאני רוצה
אני אשמח גם אם סתם יעזרו לי כאן
יש כאן דוגמה לקוד שיש לי כרגע:layout:
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/black" tools:context=".ui.streaming.StreamingFragment"> <SurfaceView android:id="@+id/surfaceView" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" /> <TextView android:id="@+id/tv_bitrate" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="16dp" android:textColor="@android:color/white" android:background="#80000000" android:padding="8dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:text="1200 Kbps" /> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="16dp" app:layout_constraintBottom_toBottomOf="parent"> <ImageView android:id="@+id/settings_button" android:layout_width="64dp" android:layout_height="64dp" android:src="@drawable/ic_settings" android:background="?attr/selectableItemBackgroundBorderless" app:tint="@color/white" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@id/b_start_stop" app:layout_constraintHorizontal_chainStyle="spread_inside" /> <ImageView android:id="@+id/b_start_stop" android:layout_width="80dp" android:layout_height="80dp" android:src="@drawable/stream_icon" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toEndOf="@id/settings_button" app:layout_constraintEnd_toStartOf="@id/switch_camera" /> <ImageView android:id="@+id/switch_camera" android:layout_width="64dp" android:layout_height="64dp" android:src="@drawable/switch_icon" android:background="?attr/selectableItemBackgroundBorderless" app:tint="@color/white" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/b_start_stop" /> </androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
קוטלין:
package com.iam699030.tore.ui.streaming import android.Manifest import android.content.pm.PackageManager import android.os.Build import android.os.Bundle import android.util.Log import android.view.* import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.textfield.TextInputEditText import com.iam699030.tore.R import com.iam699030.tore.databinding.FragmentStreamingBinding import com.pedro.common.ConnectChecker import com.pedro.library.rtmp.RtmpCamera1 import java.util.* class StreamingFragment : Fragment(), ConnectChecker, SurfaceHolder.Callback { private var _binding: FragmentStreamingBinding? = null private val binding get() = _binding!! private lateinit var rtmpCamera1: RtmpCamera1 private val permissionLauncher = registerForActivityResult( ActivityResultContracts.RequestMultiplePermissions() ) { permissions -> val allGranted = permissions.entries.all { it.value } if (allGranted) { // אם ניתנו הרשאות, אפשר להתחיל תצוגה מקדימה rtmpCamera1.startPreview() } else { Toast.makeText(requireContext(), "נדרשות הרשאות מצלמה ואודיו", Toast.LENGTH_SHORT).show() } } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { _binding = FragmentStreamingBinding.inflate(inflater, container, false) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // אתחול המצלמה והמאזין לתצוגה rtmpCamera1 = RtmpCamera1(binding.surfaceView, this) binding.surfaceView.holder.addCallback(this) setupListeners() requestPermissions() } private fun requestPermissions() { if (!hasPermissions()) { permissionLauncher.launch(REQUIRED_PERMISSIONS) } } private fun hasPermissions(): Boolean { return REQUIRED_PERMISSIONS.all { ContextCompat.checkSelfPermission(requireContext(), it) == PackageManager.PERMISSION_GRANTED } } private fun setupListeners() { binding.bStartStop.setOnClickListener { if (!rtmpCamera1.isStreaming) { showLessonNameDialog() } else { rtmpCamera1.stopStream() binding.bStartStop.setImageResource(R.drawable.stream_icon) binding.tvBitrate.text = "" } } binding.switchCamera.setOnClickListener { try { rtmpCamera1.switchCamera() } catch (e: Exception) { Toast.makeText(requireContext(), e.message, Toast.LENGTH_SHORT).show() } } } private fun showLessonNameDialog() { val dialogView = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_lesson_name, null) val editText = dialogView.findViewById<TextInputEditText>(R.id.dialogLessonNameEditText) MaterialAlertDialogBuilder(requireContext()) .setTitle("הזן שם לשיעור") .setView(dialogView) .setNegativeButton("ביטול", null) .setPositiveButton("התחל שידור") { _, _ -> val lessonName = editText.text.toString().trim() if (lessonName.isNotEmpty()) { startStream(lessonName) } } .show() } private fun startStream(lessonName: String) { if (!rtmpCamera1.isRecording) { if (rtmpCamera1.prepareAudio() && rtmpCamera1.prepareVideo()) { val hostIp = "10.0.2.2" val rtmpUrl = "rtmp://$hostIp:1935/app/$lessonName" rtmpCamera1.startStream(rtmpUrl) binding.bStartStop.setImageResource(R.drawable.stream_stop_icon) } else { Toast.makeText(requireContext(), "שגיאה בהכנת השידור", Toast.LENGTH_SHORT).show() } } } // --- מימוש ConnectChecker --- override fun onConnectionSuccess() { activity?.runOnUiThread { Toast.makeText(requireContext(), "התחברות הצליחה", Toast.LENGTH_SHORT).show() } } override fun onConnectionFailed(reason: String) { activity?.runOnUiThread { Toast.makeText(requireContext(), "התחברות נכשלה: $reason", Toast.LENGTH_SHORT).show() rtmpCamera1.stopStream() binding.bStartStop.setImageResource(R.drawable.stream_icon) } } override fun onNewBitrate(bitrate: Long) { activity?.runOnUiThread { binding.tvBitrate.text = "${bitrate / 1024} Kbps" } } override fun onDisconnect() { activity?.runOnUiThread { Toast.makeText(requireContext(), "התנתק", Toast.LENGTH_SHORT).show() binding.bStartStop.setImageResource(R.drawable.stream_icon) } } override fun onAuthError() {} override fun onAuthSuccess() {} override fun onConnectionStarted(url: String) {} // --- מימוש SurfaceHolder.Callback --- override fun surfaceCreated(holder: SurfaceHolder) {} override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { // ★★★ מתחילים את התצוגה המקדימה רק כאן, כשהמידות סופיות ★★★ rtmpCamera1.startPreview() } override fun surfaceDestroyed(holder: SurfaceHolder) { if (rtmpCamera1.isStreaming) rtmpCamera1.stopStream() rtmpCamera1.stopPreview() } override fun onDestroyView() { super.onDestroyView() _binding = null } companion object { private val REQUIRED_PERMISSIONS = mutableListOf( Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA, ).apply { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { this.add(Manifest.permission.POST_NOTIFICATIONS) } }.toTypedArray() } }
-
היי שלום
אני מחפש מפתח קוטלין שיודע לעבוד עם ספריית RootEncoder (ספריית סטרימינג) שרת OME ורכיב מצלמה
דרושים לי הדברים הבאים:
1 כתיבת ה layout של המסך עם רכיבי המצלמה (כפתורים תצוגה קדימה וכד׳)
2 כתיבת קוד קוטלין שמטפל בכל הרכיבים במסך ומזרים את התשדיר לשרת OME
יש לי כרגע קוד של המסך אבל אני לא מצליח להבין למה הוא מורח את הצילום על כל המסך ולמה כשאני מסובב את המכשיר התמונה מתעוותת
אני משלם 200₪ על התוצאה שאני רוצה
אני אשמח גם אם סתם יעזרו לי כאן
יש כאן דוגמה לקוד שיש לי כרגע:layout:
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/black" tools:context=".ui.streaming.StreamingFragment"> <SurfaceView android:id="@+id/surfaceView" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" /> <TextView android:id="@+id/tv_bitrate" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="16dp" android:textColor="@android:color/white" android:background="#80000000" android:padding="8dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:text="1200 Kbps" /> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="16dp" app:layout_constraintBottom_toBottomOf="parent"> <ImageView android:id="@+id/settings_button" android:layout_width="64dp" android:layout_height="64dp" android:src="@drawable/ic_settings" android:background="?attr/selectableItemBackgroundBorderless" app:tint="@color/white" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@id/b_start_stop" app:layout_constraintHorizontal_chainStyle="spread_inside" /> <ImageView android:id="@+id/b_start_stop" android:layout_width="80dp" android:layout_height="80dp" android:src="@drawable/stream_icon" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toEndOf="@id/settings_button" app:layout_constraintEnd_toStartOf="@id/switch_camera" /> <ImageView android:id="@+id/switch_camera" android:layout_width="64dp" android:layout_height="64dp" android:src="@drawable/switch_icon" android:background="?attr/selectableItemBackgroundBorderless" app:tint="@color/white" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/b_start_stop" /> </androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
קוטלין:
package com.iam699030.tore.ui.streaming import android.Manifest import android.content.pm.PackageManager import android.os.Build import android.os.Bundle import android.util.Log import android.view.* import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.textfield.TextInputEditText import com.iam699030.tore.R import com.iam699030.tore.databinding.FragmentStreamingBinding import com.pedro.common.ConnectChecker import com.pedro.library.rtmp.RtmpCamera1 import java.util.* class StreamingFragment : Fragment(), ConnectChecker, SurfaceHolder.Callback { private var _binding: FragmentStreamingBinding? = null private val binding get() = _binding!! private lateinit var rtmpCamera1: RtmpCamera1 private val permissionLauncher = registerForActivityResult( ActivityResultContracts.RequestMultiplePermissions() ) { permissions -> val allGranted = permissions.entries.all { it.value } if (allGranted) { // אם ניתנו הרשאות, אפשר להתחיל תצוגה מקדימה rtmpCamera1.startPreview() } else { Toast.makeText(requireContext(), "נדרשות הרשאות מצלמה ואודיו", Toast.LENGTH_SHORT).show() } } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { _binding = FragmentStreamingBinding.inflate(inflater, container, false) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // אתחול המצלמה והמאזין לתצוגה rtmpCamera1 = RtmpCamera1(binding.surfaceView, this) binding.surfaceView.holder.addCallback(this) setupListeners() requestPermissions() } private fun requestPermissions() { if (!hasPermissions()) { permissionLauncher.launch(REQUIRED_PERMISSIONS) } } private fun hasPermissions(): Boolean { return REQUIRED_PERMISSIONS.all { ContextCompat.checkSelfPermission(requireContext(), it) == PackageManager.PERMISSION_GRANTED } } private fun setupListeners() { binding.bStartStop.setOnClickListener { if (!rtmpCamera1.isStreaming) { showLessonNameDialog() } else { rtmpCamera1.stopStream() binding.bStartStop.setImageResource(R.drawable.stream_icon) binding.tvBitrate.text = "" } } binding.switchCamera.setOnClickListener { try { rtmpCamera1.switchCamera() } catch (e: Exception) { Toast.makeText(requireContext(), e.message, Toast.LENGTH_SHORT).show() } } } private fun showLessonNameDialog() { val dialogView = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_lesson_name, null) val editText = dialogView.findViewById<TextInputEditText>(R.id.dialogLessonNameEditText) MaterialAlertDialogBuilder(requireContext()) .setTitle("הזן שם לשיעור") .setView(dialogView) .setNegativeButton("ביטול", null) .setPositiveButton("התחל שידור") { _, _ -> val lessonName = editText.text.toString().trim() if (lessonName.isNotEmpty()) { startStream(lessonName) } } .show() } private fun startStream(lessonName: String) { if (!rtmpCamera1.isRecording) { if (rtmpCamera1.prepareAudio() && rtmpCamera1.prepareVideo()) { val hostIp = "10.0.2.2" val rtmpUrl = "rtmp://$hostIp:1935/app/$lessonName" rtmpCamera1.startStream(rtmpUrl) binding.bStartStop.setImageResource(R.drawable.stream_stop_icon) } else { Toast.makeText(requireContext(), "שגיאה בהכנת השידור", Toast.LENGTH_SHORT).show() } } } // --- מימוש ConnectChecker --- override fun onConnectionSuccess() { activity?.runOnUiThread { Toast.makeText(requireContext(), "התחברות הצליחה", Toast.LENGTH_SHORT).show() } } override fun onConnectionFailed(reason: String) { activity?.runOnUiThread { Toast.makeText(requireContext(), "התחברות נכשלה: $reason", Toast.LENGTH_SHORT).show() rtmpCamera1.stopStream() binding.bStartStop.setImageResource(R.drawable.stream_icon) } } override fun onNewBitrate(bitrate: Long) { activity?.runOnUiThread { binding.tvBitrate.text = "${bitrate / 1024} Kbps" } } override fun onDisconnect() { activity?.runOnUiThread { Toast.makeText(requireContext(), "התנתק", Toast.LENGTH_SHORT).show() binding.bStartStop.setImageResource(R.drawable.stream_icon) } } override fun onAuthError() {} override fun onAuthSuccess() {} override fun onConnectionStarted(url: String) {} // --- מימוש SurfaceHolder.Callback --- override fun surfaceCreated(holder: SurfaceHolder) {} override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { // ★★★ מתחילים את התצוגה המקדימה רק כאן, כשהמידות סופיות ★★★ rtmpCamera1.startPreview() } override fun surfaceDestroyed(holder: SurfaceHolder) { if (rtmpCamera1.isStreaming) rtmpCamera1.stopStream() rtmpCamera1.stopPreview() } override fun onDestroyView() { super.onDestroyView() _binding = null } companion object { private val REQUIRED_PERMISSIONS = mutableListOf( Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA, ).apply { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { this.add(Manifest.permission.POST_NOTIFICATIONS) } }.toTypedArray() } }
-
@איש-אמת
@לא-מתייאש אלוף בקוטלין@I-believe ולא יעשה את זה בשביל 200 ₪
-
@I-believe ולא יעשה את זה בשביל 200 ₪