Deewanالتوثيق

أسماء الحقول ورموز الأخطاء وأمثلة الشيفرة بالإنجليزية، كما تستخدمها في برنامجك.

تطبيقات الجوال

ديوان في تطبيقات React Native وFlutter وiOS وAndroid الأصلية — مضمَّناً بمكوّن واحد، أو بواجهتك الخاصة.

طريقتان لوضع اجتماع في تطبيقك. التضمين يعرض اجتماع ديوان نفسه — بكل الميزات، مشفّراً من طرف إلى طرف، وبلغة الشخص — من رابط انضمام. والمخصّص ينضم برمز ويعطيك المشاركين ومسارات الفيديو والدردشة والتفاعلات لترسم شاشاتك بنفسك. ومن في التطبيقات ومن في المتصفحات في الاجتماع نفسه في الحالتين.

لا تضع مفتاح API في تطبيق أبداً. خادمك ينشئ رابط الانضمام (POST /rooms/{id}/join-links) أو الرمز (POST /rooms/{id}/tokens) للشخص المسجّل ويعيد ذلك فقط.

التضمينالمخصّص
من خادمكرابط انضمامرمز
الواجهةواجهة ديوان بكل الميزاتواجهتك
الغرف الصارمةنعملا — لا تُصدر رموز للغرف الصارمة
Expo Goنعملا — يحتاج نسخة تطوير

اضبط redirect_url في رابط الانضمام (مثلاً https://app.example.com/meeting-done). عندما يغادر الشخص أو ينتهي الاجتماع، لا يفتحه العارض المضمَّن — بل يُبلغ تطبيقك لتغلق الشاشة.

React Native وExpo

bash
# Expo
npx expo install @deewan/react-native react-native-webview
# React Native CLI
npm install @deewan/react-native react-native-webview && npx pod-install
التضمين
import { DeewanMeetingView, requestMeetingPermissions } from "@deewan/react-native";

export function MeetingScreen({ joinUrl, onClose }: { joinUrl: string; onClose: () => void }) {
  const [ready, setReady] = useState(false);
  useEffect(() => {
    requestMeetingPermissions().then(() => setReady(true));
  }, []);
  if (!ready) return null;
  return <DeewanMeetingView joinUrl={joinUrl} onExit={onClose} onError={onClose} />;
}

أضف أذونات الكاميرا والميكروفون: على iOS ‏NSCameraUsageDescription وNSMicrophoneUsageDescription (في Expo ضمن ios.infoPlist في app.json)؛ وعلى Android ‏CAMERA وRECORD_AUDIO وMODIFY_AUDIO_SETTINGS. لا يفتح العارض إلا روابط deewan.io، ولا يمنح الكاميرا والميكروفون إلا لديوان.

المخصّص — التثبيت
npx expo install @deewan/react-native livekit-client @livekit/react-native @livekit/react-native-webrtc @livekit/react-native-expo-plugin @config-plugins/react-native-webrtc
# add "@livekit/react-native-expo-plugin" and "@config-plugins/react-native-webrtc" to "plugins" in app.json,
# then make a development build: npx expo run:ios / npx expo run:android
المخصّص
import { VideoView } from "@livekit/react-native";
import { joinMeeting, setupDeewan, type Meeting } from "@deewan/react-native/custom";

setupDeewan(); // once, when the app starts

// Your server calls POST /rooms/{id}/tokens and returns the result.
const t = await fetch("https://app.example.com/api/meeting-token").then((r) => r.json());
const meeting: Meeting = await joinMeeting(
  { serverUrl: t.server_url, token: t.token, e2eePassphrase: t.e2ee_passphrase },
  { publish: { microphone: true, camera: true } },
);

meeting.on("participants", (people) => setPeople(people));
meeting.on("message", ({ name, text }) => addChat(name, text));
// For each person:
<VideoView videoTrack={meeting.videoTrack(person.id)} style={{ flex: 1 }} />;
// Later:
await meeting.leave();

Flutter

bash
flutter pub add deewan
التضمين
import 'package:deewan/deewan.dart';

// Ask for camera and microphone first on Android (for example with permission_handler).
Navigator.of(context).push(MaterialPageRoute(
  builder: (context) => Scaffold(
    body: SafeArea(
      child: DeewanMeetingView(
        joinUrl: joinUrl, // from your server
        onExit: (_) => Navigator.of(context).pop(),
        onError: (_) => Navigator.of(context).pop(),
      ),
    ),
  ),
));
المخصّص
import 'package:deewan/custom.dart';

final meeting = await DeewanMeeting.join(
  MeetingCredentials.fromJson(tokenResponseFromYourServer),
  microphone: true,
  camera: true,
);

// meeting.room changes as people come, go, mute and speak.
ListenableBuilder(
  listenable: meeting.room,
  builder: (context, _) => GridView.count(
    crossAxisCount: 2,
    children: [
      for (final person in meeting.participants)
        if (meeting.videoTrack(person.id) case final track?) VideoTrackRenderer(track) else Center(child: Text(person.name)),
    ],
  ),
);

meeting.messages.listen((m) => print('${m.name}: ${m.text}'));
await meeting.sendMessage('Hello');
await meeting.leave();

الأذونات: على iOS ‏NSCameraUsageDescription وNSMicrophoneUsageDescription في Info.plist؛ وعلى Android ‏CAMERA وRECORD_AUDIO وMODIFY_AUDIO_SETTINGS وINTERNET في AndroidManifest.xml.

iOS وAndroid الأصليان

ضمّن رابط الانضمام في عارض الويب في النظام. أبقِه على deewan.io، وامنح الكاميرا والميكروفون لديوان فقط، وأغلق الشاشة عندما يحاول المغادرة (إلى redirect_url).

iOS — ‏WKWebView (iOS 15 فأحدث)
import UIKit
import WebKit

final class MeetingViewController: UIViewController, WKNavigationDelegate, WKUIDelegate {
    private let joinURL: URL // from your server
    init(joinURL: URL) { self.joinURL = joinURL; super.init(nibName: nil, bundle: nil) }
    required init?(coder: NSCoder) { fatalError() }

    override func loadView() {
        let config = WKWebViewConfiguration()
        config.allowsInlineMediaPlayback = true
        config.mediaTypesRequiringUserActionForPlayback = []
        let web = WKWebView(frame: .zero, configuration: config)
        web.navigationDelegate = self
        web.uiDelegate = self
        view = web
        web.load(URLRequest(url: joinURL))
    }

    // Camera and microphone for Deewan only.
    func webView(_ webView: WKWebView, requestMediaCapturePermissionFor origin: WKSecurityOrigin,
                 initiatedByFrame frame: WKFrameInfo, type: WKMediaCaptureType,
                 decisionHandler: @escaping (WKPermissionDecision) -> Void) {
        decisionHandler(origin.protocol == "https" && origin.host == "deewan.io" ? .grant : .deny)
    }

    // Anywhere but Deewan (your redirect_url) ends the meeting screen.
    func webView(_ webView: WKWebView, decidePolicyFor action: WKNavigationAction,
                 decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
        let url = action.request.url
        if url?.host == "deewan.io" || ["about", "blob", "data"].contains(url?.scheme ?? "") {
            decisionHandler(.allow)
            return
        }
        decisionHandler(.cancel)
        if action.targetFrame?.isMainFrame == true { dismiss(animated: true) }
    }
}
Android — ‏WebView
class MeetingActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // Request CAMERA and RECORD_AUDIO before opening this screen.
        val web = WebView(this)
        setContentView(web)
        web.settings.javaScriptEnabled = true
        web.settings.domStorageEnabled = true
        web.settings.mediaPlaybackRequiresUserGesture = false
        web.settings.allowFileAccess = false

        // Camera and microphone for Deewan only.
        web.webChromeClient = object : WebChromeClient() {
            override fun onPermissionRequest(request: PermissionRequest) {
                val deewan = request.origin.scheme == "https" && request.origin.host == "deewan.io"
                val media = request.resources.all {
                    it == PermissionRequest.RESOURCE_VIDEO_CAPTURE || it == PermissionRequest.RESOURCE_AUDIO_CAPTURE
                }
                if (deewan && media) request.grant(request.resources) else request.deny()
            }
        }
        // Anywhere but Deewan (your redirect_url) ends the meeting screen.
        web.webViewClient = object : WebViewClient() {
            override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
                if (request.url.host == "deewan.io") return false
                if (request.isForMainFrame) finish()
                return true
            }
        }
        web.loadUrl(requireNotNull(intent.getStringExtra("joinUrl"))) // from your server
    }
}

لواجهة مخصّصة بـ Swift أو Kotlin، استخدم حزمة LiveKit الأصلية مع ردّ الرمز: اتصل بـ server_url باستخدام token، وفعّل التشفير من طرف إلى طرف بمفتاح مشترك قيمته e2ee_passphrase (اشتقاق المفتاح الافتراضي، وحلقة مفاتيح بحجم 16، ودون تدوير). تستخدم الدردشة موضوع البيانات chat بالشكل {"id","text"}، والتفاعلات الموضوع reaction بالشكل {"emoji"}.