คำอธิบายว่าการเริ่มต้นอย่างรวดเร็วมีอะไรบ้าง
ทวิลิโอทำอะไร?
ทำหน้าที่จัดการผู้ใช้ในห้องและส่งแทร็กวิดีโอและเสียงระหว่างกัน โดยพื้นฐานแล้วสิ่งที่เหลืออยู่ให้เราทำคือ...
- ตั้งค่า UI และสภาพแวดล้อมสำหรับการเชื่อมต่อ
- จัดการเหตุการณ์ของผู้ใช้
วิธีใช้วิดีโอที่ตั้งโปรแกรมได้ Twilio
ทุกอย่างในบทความนี้นำมาจาก "เอกสาร" ของ Twilio และ "โครงการเริ่มต้นอย่างรวดเร็ว"
ขั้นแรกเราเพิ่ม SDK Twilio ที่ให้มา ประกอบด้วยฟังก์ชันและวัตถุที่จำเป็นทั้งหมด
// inside our app's build.gradle // Twilio video sdk implementation 'com.twilio:video-android:$version'
ตั้งค่ามุมมองการแสดงผล
จากนั้นเราสามารถใช้ videoView จาก SDK เพื่อแสดงวิดีโอบนหน้าจอได้
import com.twilio.video.VideoView;
เวอร์ชัน xml คือ
<com.twilio.video.VideoView
android:id="@+id/primary_video_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:keepScreenOn="true"
/>
และเวอร์ชันโค้ดคือ
VideoView v = new VideoView(context);
v.setKeepScreenOn(true);
ทำให้อุปกรณ์ตื่นอยู่เสมอ
โปรดสังเกตว่าฉันใส่แอตทริบิวต์ setKeepScreenon เนื่องจากเรามักไม่ต้องการให้โทรศัพท์เข้าสู่โหมดสลีประหว่างแฮงเอาท์วิดีโอ สะดวกมากที่ keepScreenOn
สามารถใช้กับมุมมองย่อยใดก็ได้ และจะทำงานในลักษณะเดียวกับที่ใช้ในมุมมองพาเรนต์
เชื่อมต่อกับห้อง
สร้างโทเค็นการเข้าถึง
ผู้ใช้ทุกคนจำเป็นต้องใช้โทเค็นการเข้าถึงเพื่อเชื่อมต่อกับห้อง เป็นเอกลักษณ์สำหรับผู้ใช้ทุกคนในห้องประชุมทุกห้อง ขั้นตอนนี้มักจะทำในแบ็กเอนด์
// taken from
https://www.twilio.com/docs/iam/access-tokens?code-sample=code-creating-an-access-token-video-2&code-language=Java&code-sdk-version=7.ximport com.twilio.jwt.accesstoken.AccessToken; import com.twilio.jwt.accesstoken.VideoGrant; public class TokenGenerator { public static void main(String[] args) { // Required for all types of tokens String twilioAccountSid = "ACxxxxxxxxxxxx"; String twilioApiKey = "SKxxxxxxxxxxxx"; String twilioApiSecret = "xxxxxxxxxxxxxx"; // Required for Video String identity = "user"; // Create Video grant VideoGrant grant = new VideoGrant().setRoom("cool room"); // Create access token AccessToken token = new AccessToken.Builder( twilioAccountSid, twilioApiKey, twilioApiSecret ).identity(identity).grant(grant).build(); System.out.println(token.toJwt()); } }
จากนั้นในแอปพลิเคชันส่วนหน้าของเรา เราสามารถเชื่อมต่อกับห้องโดยใช้โทเค็นการเข้าถึงได้ ชื่อห้องถูกกำหนดไว้แล้วเมื่อสร้างโทเค็นการเข้าถึง ดังนั้นอาร์กิวเมนต์ที่นี่จะไม่ส่งผลต่อห้องที่เราจะเชื่อมต่อจริงๆ
// taken from https://www.twilio.com/docs/video/android-getting-started public void connectToRoom(String roomName) { ConnectOptions connectOptions = new ConnectOptions.Builder(accessToken) .roomName(roomName) .audioTracks(localAudioTracks) .videoTracks(localVideoTracks) .dataTracks(localDataTracks) .build(); room = Video.connect(context, connectOptions, roomListener); }
สร้างแทร็กวิดีโอและเสียงในเครื่อง
เพื่อให้ connectToRoom()
ทำงาน เราต้องแชร์วิดีโอและเสียงของเรากับผู้เข้าร่วมคนอื่นๆ ผ่านทางแทร็ก
private void createAudioAndVideoTracks() { // Share your microphone localAudioTrack = LocalAudioTrack.create(this, true, LOCAL_AUDIO_TRACK_NAME); // Share your camera cameraCapturerCompat = new CameraCapturerCompat(this, getAvailableCameraSource()); localVideoTrack = LocalVideoTrack.create(this, true, cameraCapturerCompat.getVideoCapturer(), LOCAL_VIDEO_TRACK_NAME); primaryVideoView.setMirror(true); localVideoTrack.addRenderer(primaryVideoView); localVideoView = primaryVideoView; }
ขออนุญาติไมค์และกล้องครับ
ในรายการของเรา เราต้องการสิ่งเหล่านี้ทั้งหมด
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
จากนั้นเราก็สามารถตรวจสอบและขออนุญาตได้ในที่สุด
private boolean checkPermissionForCameraAndMicrophone() { int resultCamera = ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA); int resultMic = ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO); return resultCamera == PackageManager.PERMISSION_GRANTED && resultMic == PackageManager.PERMISSION_GRANTED; } private void requestPermissionForCameraAndMicrophone() { // request permission in fragment requestPermissions( new String[]{Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO}, CAMERA_MIC_PERMISSION_REQUEST_CODE); }
และเราต้องจับเหตุการณ์เมื่อได้รับอนุญาตแล้ว
@Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { if (requestCode == CAMERA_MIC_PERMISSION_REQUEST_CODE && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED ) { Toast.makeText(context, "Permission granted", Toast.LENGTH_LONG).show(); } }
จัดการเหตุการณ์ของผู้ใช้
Twilio จะรายงานเหตุการณ์ต่างๆ เช่น ผู้เข้าร่วมไม่ได้เชื่อมต่อ ถูกตัดการเชื่อมต่อผ่านการโทรกลับ เราต้องจัดให้มีอินเทอร์เฟซ roomListener()
เมื่อเราเชื่อมต่อกับห้อง
นอกจากการตั้งค่าผู้ฟังสำหรับห้องแล้ว เราควรแนบผู้ฟังกับผู้เข้าร่วมทุกคนในห้องด้วย ผู้ฟังรายนี้จะติดตามสถานะวิดีโอและเสียงของผู้เข้าร่วมรายนั้น
เราสามารถดูวิธีการทั้งหมดสำหรับการโทรกลับเหล่านี้ได้โดยดูจาก "ตัวอย่างการเริ่มต้นอย่างรวดเร็ว" ของ Twilio
ด้านล่างนี้เป็นตัวอย่างสำหรับ roomListener()
สิ่งที่เราต้องการทำที่นี่คือทันทีที่มีการเรียก onConnected()
เราจะเรียกผู้เข้าร่วมทั้งหมดในห้องและแนบ remoteParticipantListener()
ไว้กับแต่ละคน
แน่นอนว่า เรายังต้องแนบผู้ฟังกับผู้เข้าร่วมที่จะเข้าร่วมห้องในอนาคตด้วย
private Room.Listener roomListener() { return new Room.Listener() { @Override public void onConnected(Room room) { Toast.makeText(MainActivity.this, "Connected to room " + room.getName(), Toast.LENGTH_LONG).show(); localParticipant = room.getLocalParticipant(); List<RemoteParticipant> remoteParticipants = room.getRemoteParticipants(); for (RemoteParticipant remoteParticipant : remoteParticipants) { addRemoteParticipant(remoteParticipant); } // adding the participant does not mean their video track is ready to be subscribed // display the videos in the onVideoTrackSubscribed callback } @Override public void onParticipantConnected(Room room, RemoteParticipant remoteParticipant) { addRemoteParticipant(remoteParticipant); } @Override public void onParticipantDisconnected(Room room, RemoteParticipant remoteParticipant) { removeRemoteParticipant(remoteParticipant); } }
เนื่องจากนี่คืออินเทอร์เฟซ Java เราจึงต้องทำตามวิธีการที่จำเป็นทั้งหมดให้ครบถ้วน แต่โชคดีที่ชื่อวิธีการนั้นอธิบายได้ในตัว และตรรกะในการแสดงและลบวิดีโอของผู้เข้าร่วมออกจาก UI นั้นตรงไปตรงมา
ด้านล่างนี้เป็นตัวอย่างสำหรับการเรียกกลับ remoteParticipantListener()
โปรดจำไว้ว่านี่คือผู้ฟังสำหรับผู้เข้าร่วมแต่ละคน ดังนั้นให้อัปเดตการแสดงผลที่ถูกต้องสำหรับผู้เข้าร่วมที่ถูกต้อง
// add listener to each participant when you joined a room @SuppressLint("SetTextI18n") private RemoteParticipant.Listener remoteParticipantListener() { return new RemoteParticipant.Listener() { @Override public void onAudioTrackPublished(RemoteParticipant remoteParticipant, RemoteAudioTrackPublication remoteAudioTrackPublication) { Log.i(TAG, String.format("onAudioTrackPublished: " + "[RemoteParticipant: identity=%s], " + "[RemoteAudioTrackPublication: sid=%s, enabled=%b, " + "subscribed=%b, name=%s]", remoteParticipant.getIdentity(), remoteAudioTrackPublication.getTrackSid(), remoteAudioTrackPublication.isTrackEnabled(), remoteAudioTrackPublication.isTrackSubscribed(), remoteAudioTrackPublication.getTrackName())); } @Override public void onAudioTrackUnpublished(RemoteParticipant remoteParticipant, RemoteAudioTrackPublication remoteAudioTrackPublication) { Log.i(TAG, String.format("onAudioTrackUnpublished: " + "[RemoteParticipant: identity=%s], " + "[RemoteAudioTrackPublication: sid=%s, enabled=%b, " + "subscribed=%b, name=%s]", remoteParticipant.getIdentity(), remoteAudioTrackPublication.getTrackSid(), remoteAudioTrackPublication.isTrackEnabled(), remoteAudioTrackPublication.isTrackSubscribed(), remoteAudioTrackPublication.getTrackName())); } ......
ทำความสะอาดและปล่อยทรัพยากร
สุดท้ายนี้ อย่าลืมจัดการกับการเชื่อมต่อใหม่และการตัดการเชื่อมต่อตามวงจรชีวิตกิจกรรมของเรา
หากแอปอื่นๆ ที่มีลำดับความสำคัญสูงกว่าขัดจังหวะแอปหรือผู้ใช้ของเราปิดหน้าจอด้วยตนเองโดยการคลิกปุ่มเปิด/ปิด เราควรตัดการเชื่อมต่อจากห้อง เนื่องจากผู้ใช้อาจไม่กลับมา และ Twilio จะยังคงนับนาทีการบริการและเรียกเก็บเงินต่อไป
ดังนั้นวิธี onPause() ของเราควรมีการตัดการเชื่อมต่อจากห้องเป็นขั้นต่ำและปล่อยทรัพยากรกล้องและไมโครโฟนในโทรศัพท์ของเรา
@Override public void onPause(){ super.onPause(); /* * Release the local video track before going in the background. This ensures that the * camera can be used by other applications while this app is in the background. */ if (localVideoTrack != null) { /* * If this local video track is being shared in a Room, unpublish from room before * releasing the video track. Participants will be notified that the track has been * unpublished. */ if (localParticipant != null) { localParticipant.unpublishTrack(localVideoTrack); } localVideoTrack.release(); localVideoTrack = null; } /* * Always disconnect from the room before leaving the Activity to * ensure any memory allocated to the Room resource is freed. */ if (room != null && room.getState() != Room.State.DISCONNECTED) { room.disconnect(); clearVideoDisplays(); } |
และใน onResume()
เราจะเชื่อมต่อผู้ใช้กับห้องอีกครั้ง หากผู้ใช้กลับมาที่กิจกรรมของเรา
วิธีการส่งเสียงไปยังอุปกรณ์ต่าง ๆ
ในกรณีที่ผู้ใช้เสียบปลั๊กอุดหูหรือเชื่อมต่อกับหูฟังบลูทูธ แอปของเราต้องตรวจจับเหตุการณ์นี้และกำหนดเส้นทางแทร็กเสียงไปยังอุปกรณ์นั้น
ไลบรารี AudioSwitch ของ Twilio ช่วยให้เราทำสิ่งนั้นได้
ไลบรารีจะเลือกอุปกรณ์โดยอัตโนมัติตามลำดับความสำคัญต่อไปนี้: BluetoothHeadset -> WiredHeadset -> Earpiece -> Speakerphone
หากเรากำลังทำแอปการประชุมผ่านวิดีโอ เราจะเลือกใช้สปีกเกอร์โฟนมากกว่าหูฟัง เราสามารถกำหนดลำดับความสำคัญในตัวสร้างได้
List<Class<? extends AudioDevice>> preferredDevices = new ArrayList<>(); preferredDevices.add(BluetoothHeadset.class); preferredDevices.add(WiredHeadset.class); preferredDevices.add(Speakerphone.class); AudioSwitch audioSwitch = new AudioSwitch(context, false, focusChange -> {}, preferredDevices);
- สร้างวัตถุ audioSwitch ด้วยรายการลำดับความสำคัญ
- ลงทะเบียนการโทรกลับ audioSwitch.start() ก่อนที่จะโทรเปิดใช้งาน
- โทร audioSwitch.activate() เมื่อเชื่อมต่อ ToRoom
- เรียก audioSwitch.deactive() เมื่อตัดการเชื่อมต่อจากห้อง
- เรียก audioSwitch.stop() ไปที่ onDestroy()
วิธีทำให้หน้าจอเป็นสีดำเมื่อผู้เข้าร่วมคนอื่นปิดการใช้งานวิดีโอของตน
คุณอาจสังเกตเห็นว่าหน้าจอของคุณแสดงเฟรมสุดท้ายที่ค้างของผู้เข้าร่วมรายอื่นเมื่อเขาปิดใช้งานวิดีโอของเขา วิธีที่เหมาะสมกว่าในการจัดการสิ่งนี้คือการแสดงหน้าจอสีดำ
ฉันไม่พบการตั้งค่าใดๆ ที่ทำให้หน้าจอเป็นสีดำโดยอัตโนมัติ ในที่สุดฉันก็สร้างเค้าโครงข้อจำกัดโดยตั้งค่าพื้นหลังเป็นสีดำ ในตอนแรกทำเครื่องหมายการมองเห็นเป็น GONE และเมื่อฉันได้รับแจ้งให้ผู้เข้าร่วมปิดการใช้งานการโทรกลับทางวิดีโอ ฉันจะตั้งค่ามุมมองกลับไปเป็นมองเห็นได้เพื่อปกปิดเฟรมสุดท้ายที่ค้าง
วิธีตรวจจับผู้พูดหลัก
บางครั้งเราอาจต้องการตอบสนองต่อผู้พูด เช่นเดียวกับการใส่วิดีโอของผู้เข้าร่วมที่กำลังพูดอยู่ในหน้าต่างหลัก
Twilio ให้การตั้งค่า enableDominantSpeaker
แก่เราเพื่อตรวจจับเหตุการณ์นี้
ดู "https://www.twilio.com/docs/video/detecting-dominant-speaker" เพื่อเปิดใช้งานการตั้งค่านี้ใน ConnectOptions
ตอนนี้เรามี onDominantSpeakerChanged
โทรกลับแล้ว
วิธีหลีกเลี่ยงการได้ยินเสียงสะท้อน
ฉันพบปัญหาเสียงสะท้อนระหว่าง Samsung และ iPhone ผู้ใช้จะได้ยินเสียงของเขากลับมาจากอุปกรณ์
การใช้การตั้งค่าที่แนะนำจาก "https://github.com/twilio/video-quickstart-android#troubleshooting-audio" ช่วยแก้ไขปัญหานี้ได้