القائمة الرئيسية

الصفحات

كيفية برمجة تطبيق كاميرا مراقبة على Android Studio Java

code surveillance camera app on Android Studio using Java ، How to code security camera app on Android Studio Java، كيفية برمجة تطبيق كاميرا مراقبة على Android Studio Java، Android UI، XML Layout، Activity، Fragment، Material Design، تطبيق كاميرا مراقبة، Android UI، XML Layout، Activity، Fragment، Material Design، برمجة تطبيق كاميرا مراقبة عن بعد على اندرويد ستوديو بلغة جافا، تطبيق V380 Pro، Android Fragment، SurfaceView، SurfaceHolder، MediaCodec، RTSP Client، ONVIF Client، AsyncTask، ExecutorService، Android Activity، DrawerLayout، NavigationView، Toolbar، MenuItem، Intent، Android Activity، EditText، Button، SharedPreferences، API Request، JSON، Android UI، XML Layout، Activity، Fragment، Material Design، Android Studio، مشروع جديد، Java، اسم التطبيق، حزمة التطبيق، بناء تطبيق كاميرا مراقبة عن بعد احترافي على Android Studio (Java)، تطبيق كاميرا مراقبة، Android Studio، Java، واجهة مستخدم Android، تسجيل حساب، مصادقة المستخدم، قائمة جانبية، إعدادات التطبيق، تسجيل الفيديو، مدة التسجيل، حفظ الفيديو، الذاكرة المحلية، الصفحة الرئيسية، بث فيديو مباشر، RTSP، ONVIF، خيارات الفيديو، جودة الفيديو، تحريك PTZ، صوت ثنائي الاتجاه، V380 Pro، How to code a surveillance camera app on Android Studio using Java، How to code security camera app on Android Studio Java،



كيفية برمجة تطبيق كاميرا مراقبة على Android Studio Java



يهدف هذا المقال إلى تقديم دليل مفصل حول كيفية بناء تطبيق كاميرا مراقبة عن بعد 
احترافي لنظام Android باستخدام لغة Java وبيئة تطوير Android Studio.
 سنغطي الخطوات الأساسية لتصميم واجهة المستخدم، وتنفيذ وظائف تسجيل الحساب والمصادقة، 
وإنشاء قائمة جانبية للإعدادات، وعرض بث الفيديو المباشر، وتوفير خيارات
 متقدمة للتحكم في الفيديو والصوت.
 يهدف هذا الدليل إلى محاكاة تجربة المستخدم الاحترافية لتطبيقات مثل V380 Pro.


* ملاحظة هامة: هذا المقال سيركز على جانب تطبيق Android (الواجهة الأمامية). 
ستحتاج إلى واجهة خلفية (Backend) منفصلة
 (يمكن بناؤها باستخدام لغات مثل Python أو Node.js كما تم شرحه سابقًا) 
للتعامل مع تسجيل الكاميرات، وإدارة المستخدمين، وتوفير بث الفيديو عبر 
بروتوكولات مثل RTSP أو ONVIF. سيفترض هذا الدليل وجود واجهة خلفية
 جاهزة يمكن لتطبيق Android التواصل معها عبر واجهات برمجة التطبيقات (APIs).


الخطوات الأساسية لتطوير تطبيق كاميرا مراقبة احترافي على Android:



1. تهيئة مشروع Android Studio:

افتح Android Studio وابدأ مشروعًا جديدًا.
اختر "Empty Activity" أو "Basic Views Activity".
قم بتكوين اسم التطبيق وحزمة التطبيق وموقع الحفظ.
اختر لغة Java كـ "Language".
اختر الحد الأدنى من SDK المناسب لتوافق أوسع.

2. تصميم واجهة المستخدم (UI):

* شاشة تسجيل الحساب (activity_register.xml):

XML




<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="إنشاء حساب جديد"
        android:textSize="20sp"
        android:textStyle="bold"
        android:layout_gravity="center_horizontal"
        android:layout_marginBottom="16dp" />

    <com.google.android.material.textfield.TextInputLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/editTextRegisterEmail"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="البريد الإلكتروني"
            android:inputType="textEmailAddress" />
    </com.google.android.material.textfield.TextInputLayout>

    <com.google.android.material.textfield.TextInputLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp">
        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/editTextRegisterPassword"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="كلمة المرور"
            android:inputType="textPassword" />
    </com.google.android.material.textfield.TextInputLayout>

    <com.google.android.material.textfield.TextInputLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp">
        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/editTextRegisterConfirmPassword"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="تأكيد كلمة المرور"
            android:inputType="textPassword" />
    </com.google.android.material.textfield.TextInputLayout>

    <Button
        android:id="@+id/buttonRegister"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="إنشاء حساب"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="16dp" />

    <TextView
        android:id="@+id/textViewLoginLink"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="لديك حساب بالفعل؟ تسجيل الدخول"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="16dp"
        android:clickable="true"
        android:focusable="true" />

</LinearLayout>


--

* شاشة تسجيل الدخول (activity_login.xml): 
(تصميم مشابه لشاشة التسجيل مع حقول البريد الإلكتروني وكلمة المرور وزر تسجيل الدخول ورابط للتسجيل).

* القائمة الجانبية (navigation_drawer.xml و menu/drawer_menu.xml):
- navigation_drawer.xml: تخطيط يحتوي على NavigationView.
drawer_menu.xml: ملف قائمة يحدد عناصر القائمة الجانبية 
(إعدادات التطبيق، التسجيلات، مدة التسجيل، حفظ الفيديو/الصور).

XML




<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <group android:checkableBehavior="single">
        <item
            android:id="@+id/nav_settings"
            android:icon="@drawable/ic_settings"
            android:title="إعدادات التطبيق" />
        <item
            android:id="@+id/nav_recordings"
            android:icon="@drawable/ic_video"
            android:title="التسجيلات" />
        <item
            android:id="@+id/nav_recording_duration"
            android:icon="@drawable/ic_timer"
            android:title="مدة التسجيل" />
        <item
            android:id="@+id/nav_save_location"
            android:icon="@drawable/ic_folder"
            android:title="حفظ الفيديو/الصور" />
    </group>
</menu>


--

* الصفحة الرئيسية (activity_main.xml):

XML




<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout 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:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:openDrawer="start">

    <include
        layout="@layout/app_bar_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <com.google.android.material.navigation.NavigationView
        android:id="@+id/nav_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:fitsSystemWindows="true"
        app:headerLayout="@layout/nav_header_main"
        app:menu="@menu/drawer_menu" />

</androidx.drawerlayout.widget.DrawerLayout>


--

* app_bar_main.xml: يحتوي على Toolbar و 
FloatingActionButton (لإضافة كاميرا جديدة؟).
* content_main.xml: سيحتوي على SurfaceView أو TextureView لعرض
 بث الفيديو المباشر وعناصر التحكم في الفيديو.




* تخطيط عرض بث الفيديو (fragment_camera_view.xml):

XML




<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <SurfaceView
        android:id="@+id/surfaceViewCamera"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:orientation="horizontal"
        android:padding="8dp"
        android:background="#80000000">

        <ImageButton
            android:id="@+id/buttonHD"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:src="@drawable/ic_hd"
            android:background="?android:attr/selectableItemBackgroundBorderless"
            android:tint="@android:color/white" />

        <ImageButton
            android:id="@+id/buttonSD"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:src="@drawable/ic_sd"
            android:background="?android:attr/selectableItemBackgroundBorderless"
            android:tint="@android:color/white" />

        <ImageButton
            android:id="@+id/buttonPTZ"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:src="@drawable/ic_ptz"
            android:background="?android:attr:selectableItemBackgroundBorderless"
            android:tint="@android:color/white" />

        <ImageButton
            android:id="@+id/buttonMicrophone"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:src="@drawable/ic_microphone"
            android:background="?android:attr:selectableItemBackgroundBorderless"
            android:tint="@android:color/white" />

    </LinearLayout>

</FrameLayout>



--

* تخطيط عناصر التحكم PTZ (fragment_ptz_controls.xml):
 (تصميم أزرار أو عصا تحكم افتراضية للتحريك في جميع الاتجاهات).

3. تنفيذ وظائف تسجيل الحساب والمصادقة (RegisterActivity.java و LoginActivity.java):

- الحصول على مدخلات المستخدم من حقول البريد الإلكتروني وكلمة المرور.
- التحقق من صحة المدخلات (تنسيق البريد الإلكتروني، قوة كلمة المرور، تطابق كلمات المرور في شاشة التسجيل).
- إرسال بيانات التسجيل/تسجيل الدخول إلى الواجهة الخلفية عبر طلبات HTTP
 (باستخدام مكتبات مثل Volley أو Retrofit).
- التعامل مع استجابات الواجهة الخلفية (نجاح، فشل، رسائل الخطأ).
- تخزين رموز المصادقة أو معلومات المستخدم الأساسية باستخدام 
SharedPreferences بعد تسجيل الدخول بنجاح للحفاظ على حالة تسجيل الدخول :

* RegisterActivity.java:

Java




import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Patterns;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.Volley;

import org.json.JSONException;
import org.json.JSONObject;

public class RegisterActivity extends AppCompatActivity {

    private EditText editTextRegisterEmail, editTextRegisterPassword, editTextRegisterConfirmPassword;
    private Button buttonRegister;
    private TextView textViewLoginLink;
    private RequestQueue requestQueue;
    private SharedPreferences sharedPreferences;

    private static final String REGISTER_URL = "YOUR_BACKEND_REGISTER_API_ENDPOINT"; // استبدل بعنوان API الخاص بك

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_register);

        editTextRegisterEmail = findViewById(R.id.editTextRegisterEmail);
        editTextRegisterPassword = findViewById(R.id.editTextRegisterPassword);
        editTextRegisterConfirmPassword = findViewById(R.id.editTextRegisterConfirmPassword);
        buttonRegister = findViewById(R.id.buttonRegister);
        textViewLoginLink = findViewById(R.id.textViewLoginLink);

        requestQueue = Volley.newRequestQueue(this);
        sharedPreferences = getSharedPreferences("user_prefs", MODE_PRIVATE);

        buttonRegister.setOnClickListener(v -> registerUser());

        textViewLoginLink.setOnClickListener(v -> {
            startActivity(new Intent(RegisterActivity.this, LoginActivity.class));
            finish();
        });
    }

    private void registerUser() {
        String email = editTextRegisterEmail.getText().toString().trim();
        String password = editTextRegisterPassword.getText().toString().trim();
        String confirmPassword = editTextRegisterConfirmPassword.getText().toString().trim();

        if (TextUtils.isEmpty(email)) {
            editTextRegisterEmail.setError("البريد الإلكتروني مطلوب");
            return;
        }

        if (!Patterns.EMAIL_ADDRESS.matcher(email).matches()) {
            editTextRegisterEmail.setError("أدخل بريدًا إلكترونيًا صالحًا");
            return;
        }

        if (TextUtils.isEmpty(password)) {
            editTextRegisterPassword.setError("كلمة المرور مطلوبة");
            return;
        }

        if (password.length() < 6) {
            editTextRegisterPassword.setError("يجب أن تكون كلمة المرور 6 أحرف على الأقل");
            return;
        }

        if (!password.equals(confirmPassword)) {
            editTextRegisterConfirmPassword.setError("كلمة المرور وتأكيد كلمة المرور غير متطابقين");
            return;
        }

        JSONObject jsonParams = new JSONObject();
        try {
            jsonParams.put("email", email);
            jsonParams.put("password", password);
        } catch (JSONException e) {
            e.printStackTrace();
            return;
        }

        JsonObjectRequest request = new JsonObjectRequest(Request.Method.POST, REGISTER_URL, jsonParams,
                response -> {
                    try {
                        boolean success = response.getBoolean("success");
                        String message = response.getString("message");
                        Toast.makeText(RegisterActivity.this, message, Toast.LENGTH_SHORT).show();
                        if (success) {
                            // يمكنك حفظ رمز المصادقة هنا إذا كان الخادم يرسله
                            startActivity(new Intent(RegisterActivity.this, LoginActivity.class));
                            finish();
                        }
                    } catch (JSONException e) {
                        e.printStackTrace();
                        Toast.makeText(RegisterActivity.this, "خطأ في تحليل الاستجابة", Toast.LENGTH_SHORT).show();
                    }
                },
                error -> {
                    Toast.makeText(RegisterActivity.this, "خطأ في الاتصال بالخادم", Toast.LENGTH_SHORT).show();
                    error.printStackTrace();
                });

        requestQueue.add(request);
    }
}


--

* LoginActivity.java :
Java




import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Patterns;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.Volley;

import org.json.JSONException;
import org.json.JSONObject;

public class LoginActivity extends AppCompatActivity {

    private EditText editTextLoginEmail, editTextLoginPassword;
    private Button buttonLogin;
    private TextView textViewRegisterLink;
    private RequestQueue requestQueue;
    private SharedPreferences sharedPreferences;

    private static final String LOGIN_URL = "YOUR_BACKEND_LOGIN_API_ENDPOINT"; // استبدل بعنوان API الخاص بك

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

        editTextLoginEmail = findViewById(R.id.editTextLoginEmail);
        editTextLoginPassword = findViewById(R.id.editTextLoginPassword);
        buttonLogin = findViewById(R.id.buttonLogin);
        textViewRegisterLink = findViewById(R.id.textViewRegisterLink);

        requestQueue = Volley.newRequestQueue(this);
        sharedPreferences = getSharedPreferences("user_prefs", MODE_PRIVATE);

        buttonLogin.setOnClickListener(v -> loginUser());

        textViewRegisterLink.setOnClickListener(v -> {
            startActivity(new Intent(LoginActivity.this, RegisterActivity.class));
            finish();
        });

        // التحقق من حالة تسجيل الدخول المحفوظة
        String authToken = sharedPreferences.getString("auth_token", null);
        if (authToken != null) {
            startMainActivity();
        }
    }

    private void loginUser() {
        String email = editTextLoginEmail.getText().toString().trim();
        String password = editTextLoginPassword.getText().toString().trim();

        if (TextUtils.isEmpty(email)) {
            editTextLoginEmail.setError("البريد الإلكتروني مطلوب");
            return;
        }

        if (!Patterns.EMAIL_ADDRESS.matcher(email).matches()) {
            editTextLoginEmail.setError("أدخل بريدًا إلكترونيًا صالحًا");
            return;
        }

        if (TextUtils.isEmpty(password)) {
            editTextLoginPassword.setError("كلمة المرور مطلوبة");
            return;
        }

        JSONObject jsonParams = new JSONObject();
        try {
            jsonParams.put("email", email);
            jsonParams.put("password", password);
        } catch (JSONException e) {
            e.printStackTrace();
            return;
        }

        JsonObjectRequest request = new JsonObjectRequest(Request.Method.POST, LOGIN_URL, jsonParams,
                response -> {
                    try {
                        boolean success = response.getBoolean("success");
                        String message = response.getString("message");
                        Toast.makeText(LoginActivity.this, message, Toast.LENGTH_SHORT).show();
                        if (success) {
                            String authToken = response.getString("token"); // استقبل رمز المصادقة من الخادم
                            SharedPreferences.Editor editor = sharedPreferences.edit();
                            editor.putString("auth_token", authToken);
                            editor.apply();
                            startMainActivity();
                        } else {
                            String error = response.getString("error");
                            Toast.makeText(LoginActivity.this, error, Toast.LENGTH_SHORT).show();
                        }
                    } catch (JSONException e) {
                        e.printStackTrace();
                        Toast.makeText(LoginActivity.this, "خطأ في تحليل الاستجابة", Toast.LENGTH_SHORT).show();
                    }
                },
                error -> {
                    Toast.makeText(LoginActivity.this, "خطأ في الاتصال بالخادم", Toast.LENGTH_SHORT).show();
                    error.printStackTrace();
                });

        requestQueue.add(request);
    }

    private void startMainActivity() {
        startActivity(new Intent(LoginActivity.this, MainActivity.class));
        finish();
    }
}


--

4. تنفيذ القائمة الجانبية (MainActivity.java):

- ربط DrawerLayout و NavigationView و Toolbar في MainActivity.
- تنفيذ OnNavigationItemSelectedListener للاستماع إلى نقرات المستخدم على عناصر القائمة.




- إطلاق Activities أو Fragments مختلفة بناءً على العنصر المحدد في القائمة 
(على سبيل المثال، فتح شاشة الإعدادات، شاشة التسجيلات، إلخ.) :




Java

import android.content.Intent;
import android.os.Bundle;
import android.view.MenuItem;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;

import com.google.android.material.navigation.NavigationView;

public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {

    private DrawerLayout drawerLayout;
    private NavigationView navigationView;
    private Toolbar toolbar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        drawerLayout = findViewById(R.id.drawer_layout);
        navigationView = findViewById(R.id.nav_view);
        navigationView.setNavigationItemSelectedListener(this);

        ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar,
                R.string.navigation_drawer_open, R.string.navigation_drawer_close);
        drawerLayout.addDrawerListener(toggle);
        toggle.syncState();

        // عرض Fragment الكاميرا الافتراضي عند بدء التشغيل
        if (savedInstanceState == null) {
            getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container,
                    new CameraViewFragment()).commit();
            navigationView.setCheckedItem(R.id.nav_camera); // افترض أن لديك عنصر كاميرا في القائمة
        }
    }

    @Override
    public void onBackPressed() {
        if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
            drawerLayout.closeDrawer(GravityCompat.START);
        } else {
            super.onBackPressed();
        }
    }

    @Override
    public boolean onNavigationItemSelected(@NonNull MenuItem item) {
        switch (item.getItemId()) {
            case R.id.nav_settings:
                startActivity(new Intent(this, SettingsActivity.class));
                break;
            case R.id.nav_recordings:
                startActivity(new Intent(this, RecordingsActivity.class));
                break;
            case R.id.nav_recording_duration:
                Toast.makeText(this, "مدة التسجيل", Toast.LENGTH_SHORT).show();
                // افتح Activity أو Fragment لضبط مدة التسجيل
                break;
            case R.id.nav_save_location:
                Toast.makeText(this, "حفظ الفيديو/الصور", Toast.LENGTH_SHORT).show();
                // افتح Activity أو Fragment لاختيار موقع الحفظ
                break;
            case R.id.nav_camera:
                getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container,
                        new CameraViewFragment()).commit();
                break;
        }

        drawerLayout.closeDrawer(GravityCompat.START);
        return true;
    }
}


--

5. عرض بث الفيديو المباشر (CameraViewFragment.java):

- استخدام SurfaceView أو TextureView لعرض الفيديو.
- تنفيذ SurfaceHolder.Callback لإدارة دورة حياة السطح.
- التواصل مع الكاميرا:
*RTSP: استخدام مكتبات Java مفتوحة المصدر أو تنفيذ عميل RTSP الخاص بك
 لجلب بث الفيديو من عنوان URL الخاص بالكاميرا. قد تحتاج إلى التعامل مع
 فك ترميز الفيديو باستخدام MediaCodec.
*ONVIF: استخدام مكتبات Java ONVIF للتواصل مع الكاميرات التي تدعم
 ONVIF وجلب بث الفيديو.
- عرض الإطارات المستلمة على SurfaceView.
- التعامل مع الأخطاء وإعادة الاتصال.
- استخدام AsyncTask أو ExecutorService لتنفيذ عمليات الشبكة في الخلفية
 لتجنب حظر مؤشر واجهة المستخدم.




Java

import android.Manifest;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CameraViewFragment extends Fragment implements SurfaceHolder.Callback {

    private SurfaceView surfaceViewCamera;
    private SurfaceHolder surfaceHolder;
    private String videoUrl = "YOUR_CAMERA_RTSP_URL"; // استبدل بعنوان RTSP الخاص بك
    private ExecutorService executorService;

    private static final int CAMERA_PERMISSION_REQUEST_CODE = 100;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_camera_view, container, false);
        surfaceViewCamera = view.findViewById(R.id.surfaceViewCamera);
        surfaceHolder = surfaceViewCamera.getHolder();
        surfaceHolder.addCallback(this);
        executorService = Executors.newSingleThreadExecutor();
        return view;
    }

    @Override
    public void surfaceCreated(@NonNull SurfaceHolder holder) {
        if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(requireActivity(),
                    new String[]{Manifest.permission.CAMERA}, CAMERA_PERMISSION_REQUEST_CODE);
        } else {
            startVideoStream();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                startVideoStream();
            } else {
                Toast.makeText(requireContext(), "تم رفض إذن الكاميرا", Toast.LENGTH_SHORT).show();
            }
        }
    }

    private void startVideoStream() {
        executorService.execute(() -> {
            try {
                URL url = new URL(videoUrl);
                HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                InputStream inputStream = new BufferedInputStream(connection.getInputStream());
                // **هنا تحتاج إلى تنفيذ منطق فك ترميز الفيديو وعرض الإطارات على SurfaceView**
                // هذا الجزء معقد ويتطلب فهم بروتوكولات الفيديو ومكتبات فك الترميز مثل MediaCodec
                // أو استخدام مكتبات جاهزة تتعامل مع بث RTSP.
                Log.d("VideoStream", "بدأ بث الفيديو (تحتاج إلى تنفيذ فك الترميز والعرض)");

                // مثال بسيط لإظهار أنه يتم قراءة البيانات (للتوضيح فقط)
                byte[] buffer = new byte[1024];
                int bytesRead;
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    // يمكنك هنا معالجة البيانات المستلمة
                    Log.d("VideoStream", "تم قراءة " + bytesRead + " بايت");
                }

                inputStream.close();
                connection.disconnect();

            } catch (IOException e) {
                Log.e("VideoStream", "خطأ في بث الفيديو: " + e.getMessage());
                if (isAdded()) {
                    requireActivity().runOnUiThread(() ->
                            Toast.makeText(requireContext(), "خطأ في بث الفيديو", Toast.LENGTH_SHORT).show());
                }
            }
        });
    }

    @Override
    public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
        // يمكنك تنفيذ أي منطق لتغيير حجم العرض هنا
    }

    @Override
    public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
        if (executorService != null) {
            executorService.shutdownNow();
        }
    }
}


--

* ملاحظة هامة: عرض بث الفيديو المباشر من RTSP أو ONVIF باستخدام
 MediaCodec أو مكتبات فك ترميز أخرى هو موضوع معقد ويتطلب فهمًا عميقًا لمعالجة الفيديو. 
قد تحتاج إلى البحث عن مكتبات Java مفتوحة المصدر تتعامل مع بث 
RTSP أو ONVIF وتقوم بفك ترميز الفيديو وعرضه على SurfaceView.




6. تنفيذ خيارات الفيديو (CameraViewFragment.java):

جودة الفيديو (HD/SD):
عند النقر على أزرار HD/SD، أرسل طلبًا إلى الواجهة الخلفية (API) لإخبار الكاميرا
 بتغيير دقة البث (إذا كانت الكاميرا تدعم ذلك). قد يتطلب ذلك إعادة تهيئة بث الفيديو.
تحريك الفيديو بجميع الاتجاهات (PTZ):
إنشاء واجهة مستخدم لعناصر التحكم PTZ (أزرار الاتجاه، عصا تحكم افتراضية).
عند تفاعل المستخدم مع عناصر التحكم PTZ، أرسل أوامر إلى الواجهة الخلفية
 (API) التي بدورها سترسل أوامر التحكم PTZ إلى الكاميرا عبر بروتوكولاتها الخاصة (مثل ONVIF PTZ).
إضافة ميكروفون للتحدث من جهتين (Two-way Audio):
إرسال الصوت من التطبيق إلى الكاميرا: تسجيل صوت المستخدم من ميكروفون
 الهاتف وإرساله إلى الواجهة الخلفية عبر WebSockets أو بروتوكول مخصص.
 يجب أن تقوم الواجهة الخلفية بإعادة توجيه هذا الصوت إلى الكاميرا (إذا كانت الكاميرا تدعم إدخال الصوت).
استقبال الصوت من الكاميرا إلى التطبيق: إذا كانت الكاميرا تدعم إخراج الصوت، فستقوم 
الواجهة الخلفية باستقبال الصوت من الكاميرا وإرساله إلى التطبيق عبر WebSockets
 أو بروتوكول مخصص لعرضه على سماعات الهاتف. ستحتاج إلى استخدام
 AudioTrack في Android لتشغيل الصوت.

Java




import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

public class CameraViewFragment extends Fragment {

    private ImageButton buttonHD, buttonSD, buttonPTZ, buttonMicrophone;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_camera_view, container, false);
        buttonHD = view.findViewById(R.id.buttonHD);
        buttonSD = view.findViewById(R.id.buttonSD);
        buttonPTZ = view.findViewById(R.id.buttonPTZ);
        buttonMicrophone = view.findViewById(R.id.buttonMicrophone);

        buttonHD.setOnClickListener(v -> setVideoQuality("HD"));
        buttonSD.setOnClickListener(v -> setVideoQuality("SD"));
        buttonPTZ.setOnClickListener(v -> showPTZControls());
        buttonMicrophone.setOnClickListener(v -> toggleMicrophone());

        return view;
    }

    private void setVideoQuality(String quality) {
        // أرسل طلبًا إلى الواجهة الخلفية لتغيير جودة الفيديو
        Toast.makeText(requireContext(), "تغيير الجودة إلى " + quality, Toast.LENGTH_SHORT).show();
        // **تنفيذ منطق API للتواصل مع الواجهة الخلفية**
    }

    private void showPTZControls() {
        // اعرض Fragment أو Dialog لعناصر التحكم PTZ
        Toast.makeText(requireContext(), "عرض عناصر تحكم PTZ", Toast.LENGTH_SHORT).show();
        // **تنفيذ منطق عرض واجهة التحكم PTZ**
        // getSupportFragmentManager().beginTransaction()
        //         .replace(R.id.fragment_container, new PTZControlsFragment())
        //         .addToBackStack(null)
        //         .commit();
    }

    private void toggleMicrophone() {
        // قم بتبديل حالة الميكروفون (إرسال/استقبال الصوت)
        Toast.makeText(requireContext(), "تبديل حالة الميكروفون", Toast.LENGTH_SHORT).show();
        // **تنفيذ منطق إرسال/استقبال الصوت عبر الواجهة الخلفية**
    }
}


--

* تخطيط عناصر التحكم PTZ (fragment_ptz_controls.xml - مثال بسيط):
XML




<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="16dp"
    android:background="#80000000">

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_gravity="center_horizontal">

        <ImageButton
            android:id="@+id/buttonPanLeft"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:src="@drawable/ic_arrow_left"
            android:background="?android:attr/selectableItemBackgroundBorderless"
            android:tint="@android:color/white" />

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <ImageButton
                android:id="@+id/buttonTiltUp"
                android:layout_width="48dp"
                android:layout_height="48dp"
                android:src="@drawable/ic_arrow_up"
                android:background="?android:attr/selectableItemBackgroundBorderless"
                android:tint="@android:color/white"
                android:layout_gravity="center_horizontal" />

            <ImageButton
                android:id="@+id/buttonTiltDown"
                android:layout_width="48dp"
                android:layout_height="48dp"
                android:src="@drawable/ic_arrow_down"
                android:background="?android:attr/selectableItemBackgroundBorderless"
                android:tint="@android:color/white"
                android:layout_gravity="center_horizontal" />

        </LinearLayout>

        <ImageButton
            android:id="@+id/buttonPanRight"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:src="@drawable/ic_arrow_right"
            android:background="?android:attr/selectableItemBackgroundBorderless"
            android:tint="@android:color/white" />

    </LinearLayout>

</LinearLayout>


--

* PTZControlsFragment.java (مثال بسيط):
Java




import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

public class PTZControlsFragment extends Fragment {

    private ImageButton buttonPanLeft, buttonPanRight, buttonTiltUp, buttonTiltDown;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_ptz_controls, container, false);
        buttonPanLeft = view.findViewById(R.id.buttonPanLeft);
        buttonPanRight = view.findViewById(R.id.buttonPanRight);
        buttonTiltUp = view.findViewById(R.id.buttonTiltUp);
        buttonTiltDown = view.findViewById(R.id.buttonTiltDown);

        buttonPanLeft.setOnClickListener(v -> sendPTZCommand("left"));
        buttonPanRight.setOnClickListener(v -> sendPTZCommand("right"));
        buttonTiltUp.setOnClickListener(v -> sendPTZCommand("up"));
        buttonTiltDown.setOnClickListener(v -> sendPTZCommand("down"));

        return view;
    }

    private void sendPTZCommand(String direction) {
        Toast.makeText(requireContext(), "إرسال أمر PTZ: " + direction, Toast.LENGTH_SHORT).show();
        // **تنفيذ منطق API لإرسال أوامر PTZ إلى الواجهة الخلفية**
    }
}


--

7. تنفيذ إعدادات التطبيق، التسجيلات، مدة التسجيل، حفظ الفيديو/الصور (SettingsActivity.java, RecordingsActivity.java, إلخ.):

- إعدادات التطبيق: استخدام SharedPreferences لحفظ واستعادة تفضيلات المستخدم
 (مثل جودة الفيديو الافتراضية، مسار الحفظ المفضل).
- التسجيلات: عرض قائمة بمقاطع الفيديو والصور المسجلة 
(المخزنة محليًا أو على الواجهة الخلفية). قد تحتاج إلى واجهة مستخدم لعرض وتشغيل 
هذه التسجيلات (باستخدام VideoView أو ExoPlayer).
مدة التسجيل: السماح للمستخدم بتحديد مدة التسجيل الافتراضية.
حفظ الفيديو/الصور: السماح للمستخدم بتحديد مسار الحفظ على الذاكرة المحلية للجهاز. 
ستحتاج إلى طلب أذونات التخزين المناسبة.

* SettingsActivity.java:
Java




import android.content.SharedPreferences;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

public class SettingsActivity extends AppCompatActivity {

    private EditText editTextDefaultQuality, editTextRecordingDuration, editTextSaveFolder;
    private Button buttonSaveSettings;
    private SharedPreferences sharedPreferences;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_settings);

        editTextDefaultQuality = findViewById(R.id.editTextDefaultQuality);
        editTextRecordingDuration = findViewById(R.id.editTextRecordingDuration);
        editTextSaveFolder = findViewById(R.id.editTextSaveFolder);
        buttonSaveSettings = findViewById(R.id.buttonSaveSettings);

        sharedPreferences = getSharedPreferences("app_settings", MODE_PRIVATE);

        // استعادة الإعدادات المحفوظة
        editTextDefaultQuality.setText(sharedPreferences.getString("default_quality", "HD"));
        editTextRecordingDuration.setText(String.valueOf(sharedPreferences.getInt("recording_duration", 30)));
        editTextSaveFolder.setText(sharedPreferences.getString("save_folder", getExternalFilesDir(null).getAbsolutePath()));

        buttonSaveSettings.setOnClickListener(v -> saveSettings());
    }

    private void saveSettings() {
        String defaultQuality = editTextDefaultQuality.getText().toString().trim();
        int recordingDuration = Integer.parseInt(editTextRecordingDuration.getText().toString().trim());
        String saveFolder = editTextSaveFolder.getText().toString().trim();

        SharedPreferences.Editor editor = sharedPreferences.edit();
        editor.putString("default_quality", defaultQuality);
        editor.putInt("recording_duration", recordingDuration);
        editor.putString("save_folder", saveFolder);
        editor.apply();

        Toast.makeText(this, "تم حفظ الإعدادات", Toast.LENGTH_SHORT).show();
        finish();
    }
}


--

* activity_settings.xml (مثال بسيط):
XML




<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="إعدادات التطبيق"
        android:textSize="20sp"
        android:textStyle="bold"
        android:layout_marginBottom="16dp" />

    <com.google.android.material.textfield.TextInputLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/editTextDefaultQuality"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="جودة الفيديو الافتراضية (HD/SD)" />
    </com.google.android.material.textfield.TextInputLayout>

    <com.google.android.material.textfield.TextInputLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp">
        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/editTextRecordingDuration"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="مدة التسجيل الافتراضية (بالثواني)"
            android:inputType="number" />
    </com.google.android.material.textfield.TextInputLayout>

    <com.google.android.material.textfield.TextInputLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp">
        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/editTextSaveFolder"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="مسار حفظ الفيديو/الصور" />
    </com.google.android.material.textfield.TextInputLayout>

    <Button
        android:id="@+id/buttonSaveSettings"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="حفظ الإعدادات"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="16dp" />

</LinearLayout>


--

- RecordingsActivity.java: (ستحتاج إلى تنفيذ منطق لجلب وعرض
 قائمة التسجيلات من الذاكرة المحلية أو الواجهة الخلفية باستخدام RecyclerView أو ListView).
- تخطيط عرض التسجيلات (activity_recordings.xml و item_recording.xml).

ملاحظات هامة:
- بث الفيديو المباشر: كما ذكرت سابقًا، تنفيذ بث الفيديو المباشر بشكل احترافي
 يتطلب فهمًا عميقًا لمعالجة الفيديو والبروتوكولات (مثل RTSP أو ONVIF) وقد
 تحتاج إلى استخدام مكتبات خارجية أو تنفيذ منطق فك الترميز الخاص بك باستخدام MediaCodec.
- الصوت ثنائي الاتجاه: تنفيذ الصوت ثنائي الاتجاه يتطلب استخدام WebSockets أو
 بروتوكولات أخرى للاتصال في الوقت الفعلي بين التطبيق والواجهة الخلفية والكاميرا.
- التعامل مع الأخطاء والأداء: يجب عليك إضافة معالجة شاملة للأخطاء وتحسين الأداء في جميع أنحاء التطبيق.
- تصميم API: يجب أن يكون تصميم واجهة برمجة التطبيقات (API) بين 
تطبيق Android والواجهة الخلفية فعالًا وآمنًا.
هذه الأكواد هي نقطة بداية وستحتاج إلى تطويرها وتوسيعها بشكل كبير لبناء تطبيق كاميرا مراقبة احترافي كامل الميزات .




جدول المحتويات