أسماء الحقول ورموز الأخطاء وأمثلة الشيفرة بالإنجليزية، كما تستخدمها في برنامجك.
تطبيقات الجوال
ديوان في تطبيقات 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
# Expo
npx expo install @deewan/react-native react-native-webview
# React Native CLI
npm install @deewan/react-native react-native-webview && npx pod-installimport { 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:androidimport { 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
flutter pub add deewanimport '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).
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) }
}
}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"}.