結論
React Native for iOSにおいて、react-native-webrtc
を利用することで、WebRTCの映像をPicture-in-Picture (PiP) で表示することが可能です。
WebRTC映像のPiP表示(実機)

※ 映像はテスト用です。PCモニター裏のハブをリアルタイムで映しています。
サンプルコード
下記ソースコードは、react-native-webrtc(MIT ライセンス)上のサンプルコードを一部引用したものです。
import React, {useState, useRef} from 'react';
import {
Button,
SafeAreaView,
StyleSheet,
View,
StatusBar,
} from 'react-native';
import { Colors } from 'react-native/Libraries/NewAppScreen';
import { mediaDevices, startIOSPIP, stopIOSPIP, RTCPIPView } from 'react-native-webrtc';
const App = () => {
const view = useRef()
const [stream, setStream] = useState(null);
const start = async () => {
console.log('start');
if (!stream) {
try {
const s = await mediaDevices.getUserMedia({ video: true });
setStream(s);
} catch(e) {
console.error(e);
}
}
};
const startPIP = () => {
startIOSPIP(view);
};
const stopPIP = () => {
stopIOSPIP(view);
};
const stop = () => {
console.log('stop');
if (stream) {
stream.release();
setStream(null);
}
};
let pipOptions = {
startAutomatically: true,
fallbackView: (<View style={{ height: 50, width: 50, backgroundColor: 'red' }} />),
preferredSize: {
width: 400,
height: 800,
}
}
return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView style={styles.body}>
{
stream &&
<RTCPIPView
ref={view}
streamURL={stream.toURL()}
style={styles.stream}
iosPIP={pipOptions} >
</RTCPIPView>
}
<View
style={styles.footer}>
<Button
title = "Start"
onPress = {start} />
<Button
title = "Start PIP"
onPress = {startPIP} />
<Button
title = "Stop PIP"
onPress = {stopPIP} />
<Button
title = "Stop"
onPress = {stop} />
</View>
</SafeAreaView>
</>
);
};
const styles = StyleSheet.create({
body: {
backgroundColor: Colors.white,
...StyleSheet.absoluteFill
},
stream: {
flex: 1
},
footer: {
backgroundColor: Colors.lighter,
position: 'absolute',
bottom: 0,
left: 0,
right: 0
},
});
export default App;
import { mediaDevices, startIOSPIP, stopIOSPIP, RTCPIPView } from 'react-native-webrtc';
我々ライブラリの利用者としては、主にこれらのパーツだけでiOSでPiPを実現可能。
至極シンプルなサンプルコードが全てを物語ってるので、ここで詳細にコードの解説はしません。
さて、 react-native-webrtc
を利用すれば簡単にReactNative x iOSでPiPが実現できることは分かりましたが、実は内部的には一筋縄ではいかず、なかなかに泥臭いことをしていたようです。
次のセクションで掘り下げます。
iOSでWebRTC映像のPiP表示が難しい理由
なぜiOSでPiP表示の実装が(本来は)複雑になるのか、その技術的な背景について簡単に触れておきます。単にPiP表示を実装したいだけであれば、冒頭の「結論」セクションのみ確認して頂ければ、この先のセクションは読み飛ばしていただいて問題ありません。
原因
iOSネイティブのPiP機能が受け付ける映像形式と、WebRTCが標準で用いる描画方法に直接的な互換性がないため、iOSでWebRTC映像のPiP表示が容易ではないようです。
- iOSのPiP機能: 標準的な動画プレイヤーで使われるコンポーネントからの映像を表示するように設計されています。
-
WebRTCの映像描画: 一方で、
react-native-webrtc
が内部で利用するiOSネイティブのWebRTCモジュールは、GPUを使って直接画面に映像を描画する特殊なビューコンポーネントを利用します。
このネイティブモジュールが利用する特殊なビューは、iOSのPiP機能が直接サポートしていないため、単純に映像をPiPウィンドウに渡すことができないのです。
対応策
この問題を解決するため、react-native-webrtc
ライブラリでは、ネイティブレイヤーで巧妙な変換処理を実装しています。
具体的には react-native-webrtcにおけるiOS PiP対応のcommit を見て頂くとよいかと思います。
WebRTCから受け取ったビデオフレームを、iOSのPiP機能が扱える標準的な映像フォーマットへリアルタイムに変換し、実現しているようです。
この複雑な変換処理は、ライブラリに新しく導入された RTCPIPView
というコンポーネントが内部で全て行ってくれます。そのため、我々React Native開発者は、ネイティブ側の難しい仕組みを意識することなく、このコンポーネントを配置するだけでPiP機能を手軽に実装できるのです。
本当に感謝しかありませんね。
Androidの場合は?
AndroidでのWebRTC PiP実装ははるかにシンプルで、AndroidのWebRTC SDKが提供する SurfaceViewRenderer
の映像データは、特に複雑な変換処理なしにPiPウィンドウへ移行させることができるようです。
React NativeでAndroidのPiPを実装する場合、いくつかのライブラリが存在します。私のプロジェクトではExpoも導入していたため、expo-pip を利用しました。
現状、iOSとAndroidで異なるPiPライブラリを利用する必要があります。将来的には react-native-webrtc
がAndroidのPiPにも対応してくれると嬉しいのですが、記事執筆時点ではまだ未対応のようです。
おわりに
本記事の9割は単にライブラリのサンプルコードをペッと引用しただけのなんとも質の低いものですが、
-
react-native-webrtc
のドキュメントで何故かPiP機能について触れられていない - react-native-webrtc ios pip などでググってもgithubのissueがかろうじてヒットするくらいで見過ごしやすい
- AIに聞いても
react-native-webrtc
のPiP機能についてはまだ学習していないようで、自前実装を試みてしまった
といった沼にはまって、何時間も掛けて車輪の再発明を試みてしまう(私のような)愚かな犠牲者がこれ以上増えないことを願っての執筆でした。