การโทรผ่านวิดีโอได้กลายเป็นกิจกรรมที่สำคัญในแต่ละวันในช่วงการระบาดของ COVID-19 ด้วยการใช้คุณสมบัติต่างๆ เช่น แอปแชท การโทรด้วยเสียง และการสนทนาทางวิดีโอ ทำให้เราสามารถติดต่อกับเพื่อนและครอบครัวของเราได้
ตอนนี้ มาสร้างแอป React Native ของตัวเองกันเถอะ ซึ่งจะทำให้เราสามารถโทรผ่านวิดีโอได้
ในบทช่วยสอนนี้ เราจะมาเรียนรู้วิธีใช้งานฟีเจอร์การโทรผ่านวิดีโอในแอป React Native โดยใช้ API แฮงเอาท์วิดีโอที่ตั้งโปรแกรมได้ของ Twilio
กระบวนการนี้ค่อนข้างตรงไปตรงมา เราจะสร้างห้องประชุมทางวิดีโอและเชิญผู้อื่นเข้าร่วมห้องนั้น ในการทำเช่นนี้ เราจะต้องเข้าถึงกล้องและไมโครโฟน ดังนั้น เราจำเป็นต้องใช้อุปกรณ์สมาร์ทโฟนจริงเพื่อการทดสอบ
แพ็คเกจหลักที่เราจะใช้เพื่อเข้าถึง Twilio API คือแพ็คเกจ react-native-twilio-video-webrtc
ข้อกำหนด
- บัญชี Twilio
- อุปกรณ์ iOS หรือ Android ขั้นต่ำ 2 เครื่องสำหรับการทดสอบ
- ตอบสนองการตั้งค่า Native Environment
มาเริ่มกันเลย!
วิธีรับคีย์ Twilio API ของคุณ
ในการรับคีย์ Twilio API คุณจะต้องมีบัญชี Twilio เพื่อไปที่ URL นี้ หลังจากตั้งค่าบัญชีของคุณแล้ว คุณต้องไปที่ตำแหน่งที่กำหนดโดยภาพหน้าจอด้านล่าง:
วิธีการตั้งค่าเซิร์ฟเวอร์เพื่อจัดการการดึงโทเค็นการเข้าถึง
ในการดึงโทเค็นการเข้าถึง เราต้องสร้างโปรเจ็กต์เซิร์ฟเวอร์โหนดใหม่ สำหรับสิ่งนั้น เราจำเป็นต้องติดตั้งแพ็คเกจที่จำเป็นโดยรันคำสั่งต่อไปนี้:
yarn add dotenv express ngrok nodemon twilio
ต่อไป เราต้องเพิ่มข้อมูลรับรอง Twilio ในไฟล์ตัวแปรสภาพแวดล้อม – .env ดังที่แสดงในข้อมูลโค้ดด้านล่าง:
PORT=3000
ACCOUNT_SID=AC5ceb0847c50c91b143ce07
API_KEY_SID=SKa173c10de99a26fd86969b
API_KEY_SECRET=Czv7IjNIZJis8s7jb5FePi
ตอนนี้เราต้องสร้างปลายทาง API ขั้นแรก เราต้องนำเข้าแพ็คเกจที่จำเป็นและสร้างอินสแตนซ์อ็อบเจ็กต์เพื่อรับโทเค็นการเข้าถึงตามที่กำหนดไว้ในข้อมูลโค้ดด้านล่าง:
import 'dotenv/config';
import express from 'express';
import twilio from 'twilio';
import ngrok from 'ngrok';
const AccessToken = twilio.jwt.AccessToken;
const VideoGrant = AccessToken.VideoGrant;
const app = express();
ที่นี่ เราจะสร้างปลายทาง API เพื่อรับโทเค็นการเข้าถึง โดยใช้เมธอด get ที่จัดเตรียมโดยอินสแตนซ์ Express เราจำเป็นต้องสร้างฟังก์ชันปลายทางที่ตอบสนองด้วยโทเค็นการเข้าถึง
ภายในฟังก์ชัน เราต้องสร้างอินสแตนซ์ใหม่ด้วยข้อมูลรับรอง Twillio จากนั้นเราต้องเพิ่มชื่อผู้ใช้ที่เราได้รับจากหน้าจอลงทะเบียนบนอุปกรณ์มือถือเป็นแอตทริบิวต์ข้อมูลประจำตัว
สุดท้ายนี้ เราจะให้สิทธิ์การเข้าถึงแก่ผู้ใช้เพื่อให้สามารถใช้วิดีโอได้ จากนั้นจึงส่งคืนโทเค็น JWT กลับไปที่อุปกรณ์ นี่คือรหัสสำหรับดำเนินการทั้งหมดนี้ในตัวอย่างด้านล่าง:
app.get('/getToken', (req, res) => {
if (!req.query || !req.query.userName) {
return res.status(400).send('Username parameter is required');
}
const accessToken = new AccessToken(
process.env.ACCOUNT_SID,
process.env.API_KEY_SID,
process.env.API_KEY_SECRET,
);
// Set the Identity of this token
accessToken.identity = req.query.userName;
// Grant access to Video
var grant = new VideoGrant();
accessToken.addGrant(grant);
// Serialize the token as a JWT
var jwt = accessToken.toJwt();
return res.send(jwt);
});
นอกจากนี้เรายังเปิดเผย endpoint API ที่เราสร้างขึ้นบนอินเทอร์เน็ตสำหรับการเข้าถึงทางทิศตะวันออก สำหรับสิ่งนั้น เราสามารถใช้โค้ดจากข้อมูลโค้ดต่อไปนี้:
app.listen(process.env.PORT, () =>
console.log(`Server listening on port ${process.env.PORT}!`),
);
ngrok.connect(process.env.PORT).then((url) => {
console.log(`Server forwarded to public url ${url}`);
});
สุดท้ายนี้ เราต้องเปิดเซิฟเวอร์ตามที่แสดงในภาพหน้าจอด้านล่าง:
ที่นี่ เราได้สร้างปลายทาง API เพื่อส่งคืนโทเค็นการเข้าถึงสำเร็จแล้ว
วิธีกำหนดค่าโครงการ React Native ของเรา
ในโครงการ React Native ของเรา เราจำเป็นต้องตั้งค่าแพ็คเกจด้วยตนเอง รวมถึงกำหนดค่าการอนุญาตเพื่อเข้าถึงกล้องและไมโครโฟนสำหรับทั้งแพลตฟอร์ม Android และ iOS
แต่ก่อนอื่นเราต้องติดตั้งแพ็คเกจที่จำเป็น ซึ่งก็คือ react-navigation
และ react-native-twilio-video-webrtc
โดยการรันคำสั่งต่อไปนี้ในเทอร์มินัลโปรเจ็กต์ของเรา:
yarn add @react-navigation/native @react-navigation/stack react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context @react-native-community/masked-view react-native-dotenv react-native-permissions <https://github.com/blackuy/react-native-twilio-video-webrtc>
การตั้งค่าสำหรับ iOS
สำหรับ iOS เราต้องตั้งค่าแพ็คเกจด้วยตนเอง ขั้นแรก เราต้องเพิ่ม เป้าหมาย IOS เป็น 11 บน Podfile . นี่เป็นสิ่งจำเป็นเนื่องจาก Native Video SDK ของ Twilio รองรับเฉพาะ iOS 11.0+:
platform :ios, '11.0'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
ใน Podfile เราจำเป็นต้องตั้งค่าคำขออนุญาตตามที่กำหนดไว้ในข้อมูลโค้ดด้านล่าง:
permissions_path = '../node_modules/react-native-permissions/ios'
pod 'Permission-Camera', :path => "#{permissions_path}/Camera.podspec"
pod 'Permission-Microphone', :path => "#{permissions_path}/Microphone.podspec"
จากนั้น เราต้องเปิด info.plist และเพิ่มรหัสเพื่อขออนุญาตเข้าถึงกล้องและไมโครโฟนตามคำแนะนำในข้อมูลโค้ดด้านล่าง:
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>NSCameraUsageDescription</key>
<string>We require your permission to access the camera while in a video call</string>
<key>NSMicrophoneUsageDescription</key>
<string>We require your permission to access the microphone while in a video call</string>
ตอนนี้เราตั้งค่า iOS เสร็จแล้ว
การตั้งค่าสำหรับ Android
ก่อนอื่นเราต้องเพิ่มโค้ดต่อไปนี้ใน ./android/settings.gradle ไฟล์:
project(':react-native-twilio-video-webrtc').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-twilio-video-webrtc/android')
นอกจากนี้ เราจำเป็นต้องเพิ่มโค้ดการติดตั้งแพ็กเกจใน ./android/app/build.gradle ไฟล์:
implementation project(':react-native-twilio-video-webrtc')
สุดท้ายนี้ เราต้องนำเข้าสิ่งนี้ไปยัง MainApplication.java ไฟล์เช่นกัน:
import com.twiliorn.library.TwilioPackage;
จากนั้น เราต้องเปิดใช้งานแพ็คเกจโดยใช้โค้ดต่อไปนี้:
@Override
protected List getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List packages = new PackageList(this).getPackages();
// add the following code
packages.add(new TwilioPackage());
return packages;
}
วิธีสร้างหน้าจอห้องลงทะเบียน
ในที่นี้ เราจะสร้างหน้าจอชื่อ "ห้องลงทะเบียน" ซึ่งจะทำให้เราสามารถลงทะเบียนเข้าห้องในแอพ React Native ของวิดีโอคอล
ขั้นแรก เราต้องนำเข้าแพ็คเกจที่จำเป็นตามที่แสดงในข้อมูลโค้ดด้านล่าง:
import React, {useState, useRef, useEffect, useContext} from 'react';
import {
StyleSheet,
View,
Text,
StatusBar,
TouchableOpacity,
TextInput,
Alert,
KeyboardAvoidingView,
Platform,
ScrollView,
Dimensions,
} from 'react-native';
import {
TwilioVideoLocalView,
TwilioVideoParticipantView,
TwilioVideo,
} from 'react-native-twilio-video-webrtc';
import {NavigationContainer} from '@react-navigation/native';
import {createStackNavigator} from '@react-navigation/stack';
- react-navigation:เพื่อจัดการการนำทางของหน้าจอลงทะเบียนและหน้าจอแฮงเอาท์วิดีโอ
- react-native:แพ็คเกจนี้ช่วยให้เราจัดการสิทธิ์ในการเข้าถึงกล้องและไมโครโฟนได้
- react-native-twilio-video-webrtc:ซึ่งช่วยให้เราเข้าถึง API ที่ตั้งโปรแกรมได้ของแฮงเอาท์วิดีโอของ Twilio
วิธีการเริ่มต้นอินสแตนซ์และตัวแปร
อันดับแรก เราจะสร้างตัวอย่างสำหรับการนำทางแบบตอบสนอง จากนั้น เราจะเริ่มต้นสถานะและตัวแปรบริบทเพื่อแจกจ่ายสถานะตามที่แสดงในข้อมูลโค้ดด้านล่าง:
const Stack = createStackNavigator();
const initialState = {
isAudioEnabled: true,
status: 'disconnected',
participants: new Map(),
videoTracks: new Map(),
userName: '',
roomName: '',
token: '',
};
const AppContext = React.createContext(initialState);
const dimensions = Dimensions.get('window');
การนำทางด้วยสายรัดบูต
ใน App.js เรากำลังจะสร้างสแต็คคอนเทนเนอร์การนำทาง การใช้ Stack
ส่วนประกอบที่เราจะแจกจ่ายให้กับทุกหน้าจอโดยใช้บริบทตามที่กำหนดไว้ในข้อมูลโค้ดด้านล่าง:
export default () => {
const [props, setProps] = useState(initialState);
return (
<>
<StatusBar barStyle="dark-content" />
<AppContext.Provider value={{props, setProps}}>
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Video Call" component={VideoCallScreen} />
</Stack.Navigator>
</NavigationContainer>
</AppContext.Provider>
</>
);
};
วิธีสร้างหน้าจอลงทะเบียน
หน้าจอลงทะเบียนจะมีกล่องโต้ตอบโมดอลเพื่อรับข้อมูลรับรองของผู้ใช้และอนุญาตให้ผู้ใช้เข้าร่วมห้องสนทนาทางวิดีโอ
ขั้นแรก เราต้องดึงอุปกรณ์ประกอบฉากจากบริบทไปยัง register.js ดังแสดงในโค้ดด้านล่าง:
import React, {useState, useRef, useEffect, useContext} from 'react';
import {
checkMultiple,
request,
requestMultiple,
PERMISSIONS,
RESULTS,
} from 'react-native-permissions';
const RegisterScreen = ({navigation}) => {
const {props, setProps} = useContext(AppContext);
ต่อไป เราต้องสร้างฟังก์ชันเพื่อจัดการสิทธิ์ของกล้องและไมโครโฟน รหัสสำหรับฟังก์ชันมีอยู่ในข้อมูลโค้ดด้านล่าง:
const _checkPermissions = (callback) => {
const iosPermissions = [PERMISSIONS.IOS.CAMERA, PERMISSIONS.IOS.MICROPHONE];
const androidPermissions = [
PERMISSIONS.ANDROID.CAMERA,
PERMISSIONS.ANDROID.RECORD_AUDIO,
];
checkMultiple(
Platform.OS === 'ios' ? iosPermissions : androidPermissions,
).then((statuses) => {
const [CAMERA, AUDIO] =
Platform.OS === 'ios' ? iosPermissions : androidPermissions;
if (
statuses[CAMERA] === RESULTS.UNAVAILABLE ||
statuses[AUDIO] === RESULTS.UNAVAILABLE
) {
Alert.alert(
'Error',
'Hardware to support video calls is not available',
);
} else if (
statuses[CAMERA] === RESULTS.BLOCKED ||
statuses[AUDIO] === RESULTS.BLOCKED
) {
Alert.alert(
'Error',
'Permission to access hardware was blocked, please grant manually',
);
} else {
if (
statuses[CAMERA] === RESULTS.DENIED &&
statuses[AUDIO] === RESULTS.DENIED
) {
requestMultiple(
Platform.OS === 'ios' ? iosPermissions : androidPermissions,
).then((newStatuses) => {
if (
newStatuses[CAMERA] === RESULTS.GRANTED &&
newStatuses[AUDIO] === RESULTS.GRANTED
) {
callback && callback();
} else {
Alert.alert('Error', 'One of the permissions was not granted');
}
});
} else if (
statuses[CAMERA] === RESULTS.DENIED ||
statuses[AUDIO] === RESULTS.DENIED
) {
request(statuses[CAMERA] === RESULTS.DENIED ? CAMERA : AUDIO).then(
(result) => {
if (result === RESULTS.GRANTED) {
callback && callback();
} else {
Alert.alert('Error', 'Permission not granted');
}
},
);
} else if (
statuses[CAMERA] === RESULTS.GRANTED ||
statuses[AUDIO] === RESULTS.GRANTED
) {
callback && callback();
}
}
});
};
จากนั้น เราจำเป็นต้องเรียกใช้ฟังก์ชันตรวจสอบการอนุญาตนี้ทุกครั้งที่แอปเริ่มทำงาน เพื่อที่เราต้องเรียกใช้ฟังก์ชันภายใน useEffect
ขอตามคำแนะนำในข้อมูลโค้ดด้านล่าง:
useEffect(() => {
_checkPermissions();
}, []);
สุดท้ายนี้ เราต้องสร้างแบบฟอร์มง่ายๆ ที่มีอินพุต 2 ช่องที่ยอมรับชื่อห้องและชื่อผู้ใช้ จากนั้น เราจำเป็นต้องส่งอินพุตไปยังเซิร์ฟเวอร์เพื่อลงทะเบียนบน Twilio API รหัสสำหรับสิ่งนี้มีอยู่ในข้อมูลโค้ดด้านล่าง:
return (
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={styles.container}>
<ScrollView contentContainerStyle={styles.container}>
<View style={styles.form}>
<View style={styles.formGroup}>
<Text style={styles.text}>User Name</Text>
<TextInput
style={styles.textInput}
autoCapitalize="none"
value={props.userName}
onChangeText={(text) => setProps({...props, userName: text})}
/>
</View>
<View style={styles.formGroup}>
<Text style={styles.text}>Room Name</Text>
<TextInput
style={styles.textInput}
autoCapitalize="none"
value={props.roomName}
onChangeText={(text) => setProps({...props, roomName: text})}
/>
</View>
<View style={styles.formGroup}>
<TouchableOpacity
disabled={false}
style={styles.button}
onPress={() => {
_checkPermissions(() => {
fetch(`https://ae7a722dc260.ngrok.io/getToken?userName=${props.userName}`)
.then((response) => {
if (response.ok) {
response.text().then((jwt) => {
setProps({...props, token: jwt});
navigation.navigate('Video Call');
return true;
});
} else {
response.text().then((error) => {
Alert.alert(error);
});
}
})
.catch((error) => {
console.log('error', error);
Alert.alert('API not available');
});
});
}}>
<Text style={styles.buttonText}>Connect to Video Call</Text>
</TouchableOpacity>
</View>
</View>
</ScrollView>
</KeyboardAvoidingView>
);
เราจะได้ผลลัพธ์ตามที่แสดงในภาพหน้าจอของโปรแกรมจำลองด้านล่าง:
ที่นี่ เราสามารถสังเกตเห็นหน้าจอ Register Room ของเราด้วยรูปแบบโมดอล ซึ่งเราสามารถป้อนชื่อห้องและชื่อผู้ใช้เพื่อลงทะเบียนกับ Twilio API ที่ทำงานบนเซิร์ฟเวอร์ได้
วิธีสร้างหน้าจอแฮงเอาท์วิดีโอ
ในหน้าจอแฮงเอาท์วิดีโอ เราจะมีหน้าต่างสองบาน หน้าต่างหนึ่งสำหรับแสดงมุมมองของกล้องของเราเอง และอีกบานสำหรับแสดงมุมมองของกล้องของผู้รับ
อันดับแรก เราต้องเริ่มต้นบริบทเพื่อยอมรับสถานะต่างๆ จากนั้นเราจะสร้างตัวแปรอ้างอิงโดยใช้ useRef
ขอเข้าถึงสถานะตามที่กำหนดไว้ในข้อมูลโค้ดด้านล่าง:
const VideoCallScreen = ({navigation}) => {
const twilioVideo = useRef(null);
const {props, setProps} = useContext(AppContext);
ต่อไป เราต้องเริ่มต้นการเชื่อมต่อโดยใช้ connect
วิธีการจาก twilioVideo
ออบเจ็กต์ โดยระบุชื่อห้องและโทเค็นการเข้าถึงตามที่กำหนดไว้ในข้อมูลโค้ดด้านล่าง:
useEffect(() => {
twilioVideo.current.connect({
roomName: props.roomName,
accessToken: props.token,
});
setProps({...props, status: 'connecting'});
return () => {
_onEndButtonPress();
};
}, []);
ตอนนี้ เราต้องสร้างเทมเพลตเนื้อหาหลักสำหรับหน้าจอแฮงเอาท์วิดีโอ ที่นี่ เราแสดงมุมมองกล้องของผู้เข้าร่วมเฉพาะเมื่อมีการเชื่อมต่อและสตรีมโดยใช้การแสดงผลตามเงื่อนไข รหัสโดยรวมสำหรับสิ่งนี้มีอยู่ในข้อมูลโค้ดด้านล่าง:
{(props.status === 'connected' || props.status === 'connecting') && (
<View style={styles.callWrapper}>
{props.status === 'connected' && (
<View style={styles.grid}>
{Array.from(props.videoTracks, ([trackSid, trackIdentifier]) => (
<TwilioVideoParticipantView
style={styles.remoteVideo}
key={trackSid}
trackIdentifier={trackIdentifier}
/>
))}
</View>
)}
</View>
)}
ต่อไป เราต้องสร้างฟังก์ชันเพื่อควบคุมคุณสมบัติในวิดีโอ เช่น วางสาย ปิดเสียง และสลับกล้องหน้าและกล้องหลัง การใช้งานการเข้ารหัสของฟังก์ชันที่จำเป็นมีอยู่ในข้อมูลโค้ดด้านล่าง:
const _onEndButtonPress = () => {
twilioVideo.current.disconnect();
setProps(initialState);
};
const _onMuteButtonPress = () => {
twilioVideo.current
.setLocalAudioEnabled(!props.isAudioEnabled)
.then((isEnabled) => setProps({...props, isAudioEnabled: isEnabled}));
};
const _onFlipButtonPress = () => {
twilioVideo.current.flipCamera();
};
ในที่นี้ เราใช้ disconnect
, setLocalAudioEnabled
และ flipCamera
วิธีการจัดทำโดย twilioVideo
เพื่อเรียกใช้คุณลักษณะในวิดีโอที่จำเป็น
ตอนนี้เราจำเป็นต้องแสดงปุ่มบางปุ่มเพื่อเรียกใช้ฟังก์ชัน ในการนั้น เราจำเป็นต้องใช้โค้ดจากข้อมูลโค้ดต่อไปนี้:
<View style={styles.optionsContainer}>
<TouchableOpacity style={styles.button} onPress={_onEndButtonPress}>
<Text style={styles.buttonText}>End</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button} onPress={_onMuteButtonPress}>
<Text style={styles.buttonText}>
{props.isAudioEnabled ? 'Mute' : 'Unmute'}
</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button} onPress={_onFlipButtonPress}>
<Text style={styles.buttonText}>Flip</Text>
</TouchableOpacity>
</View>
ขั้นตอนสุดท้ายคือการเพิ่ม TwilioVideo
องค์ประกอบที่ได้รับการกำหนดค่าให้จัดการและสังเกตเหตุการณ์การโทรผ่านวิดีโอทั้งหมด การกำหนดค่าโดยรวม TwilioVideo
คอมโพเนนต์มีอยู่ในข้อมูลโค้ดด้านล่าง:
<TwilioVideo
ref={twilioVideo}
onRoomDidConnect={() => {
setProps({...props, status: 'connected'});
}}
onRoomDidDisconnect={() => {
setProps({...props, status: 'disconnected'});
navigation.goBack();
}}
onRoomDidFailToConnect={(error) => {
Alert.alert('Error', error.error);
setProps({...props, status: 'disconnected'});
navigation.goBack();
}}
onParticipantAddedVideoTrack={({participant, track}) => {
if (track.enabled) {
setProps({
...props,
videoTracks: new Map([
...props.videoTracks,
[
track.trackSid,
{
participantSid: participant.sid,
videoTrackSid: track.trackSid,
},
],
]),
});
}
}}
onParticipantRemovedVideoTrack={({track}) => {
const videoTracks = props.videoTracks;
videoTracks.delete(track.trackSid);
setProps({...props, videoTracks});
}}
/>
เราจะได้ผลลัพธ์ต่อไปนี้หากเราสามารถสร้างการเชื่อมต่อที่เหมาะสมระหว่างผู้ใช้ในห้อง:
ภาพหน้าจอด้านบนสาธิตวิดีโอคอลระหว่างผู้เข้าร่วมสองคนในห้องแชท
ด้วยเหตุนี้ เราจึงใช้ฟีเจอร์การโทรผ่านวิดีโอในแอป React Native ได้สำเร็จ
บทสรุป
บทช่วยสอนนี้มีจุดมุ่งหมายเพื่อนำเสนอทรัพยากรการเรียนรู้ระดับเริ่มต้นเกี่ยวกับวิธีการตั้งค่าการโทรผ่านวิดีโอในแอป React Native เราทำสิ่งนี้โดยใช้ API แฮงเอาท์วิดีโอที่ตั้งโปรแกรมได้ของ Twilio
เราไม่เพียงแต่ครอบคลุมส่วน React Native แต่ยังรวมถึงการใช้งาน API โดยรวมในโครงการเซิร์ฟเวอร์โหนดแยกต่างหากด้วย
ตอนนี้ ขั้นตอนต่อไปคือการเพิ่มคุณสมบัติขั้นสูง เช่น การโทรแบบไม่ระบุชื่อหรือห้องสนทนาทางวิดีโอของผู้เข้าร่วมหลายห้อง
สำหรับแรงบันดาลใจของฟีเจอร์และแอปวิดีโอคอลที่เหมาะสม คุณสามารถดู instamobile.io ที่เสนอสถานะของแอปวิดีโอแชทพร้อมฟีเจอร์อันทรงพลัง
เจอกันใหม่คราวหน้า Happy Coding!