概要
現在、AIR にはリアルタイムメディアストリーミングの最新の実装がありません。KlugMedia はそれをネイティブ拡張機能として追加します。カメラ、マイク、エンコーダー、エコーキャンセリングがひとつのネイティブセッションでまとめて動作し、ActionScript はコマンドを送信してステータスを受け取るだけです。
映像は H.264、音声は Opus で、どちらも標準の RTMP で配信・受信されます。受信した映像は AIR ステージの上にネイティブで描画されるため、古いデバイスでも鮮明でなめらかに表示されます。
KlugMedia は、App Store、Google Play、Mac で、実証の場である当社のアプリにおいて毎日使われています。
機能
- 自己完結型:ANE 1つで完結し、コアライブラリや依存 ANE の追加は不要
- ひとつのネイティブセッションでのカメラキャプチャと H.264 配信
- ネイティブの H.264 受信と表示。アスペクト比を維持し、クロップ表示と回転にも対応
- ハードウェアエコーキャンセリング付きの双方向 Opus 音声。双方が同時に話せます
- ネイティブのカメラプレビュー、カメラ切り替え、トーチ、ズーム
- カメラ映像から直接スナップショットを取得
- Klug ストリーミングサーバー上でのライブストリームの MP4 録画(映像と音声)
- 低速なネットワークでのビットレート自動調整
- Wi-Fi の瞬断を吸収し、自動的に低遅延へ戻る適応型の音声バッファ
- Mac では、内蔵カメラと USB カメラを約1秒で切り替え
- 標準の RTMP を使用するため、一般的なメディアサーバーでも動作
ストリーミングサーバー
KlugMedia には、拡張機能と一緒に開発され、AIR アプリ向けに調整された専用のサーバーソフトウェアが付属します。ストリーム管理、信頼性の高い再接続、双方向音声リレー、MP4 録画、Webブラウザでのライブ視聴に対応します。
対応プラットフォーム
Mac 上の iPad アプリ
Apple シリコン搭載の Mac では、AIR アプリの iOS ビルドを iPad アプリとして実行できます。KlugMedia は、内蔵の FaceTime カメラや外付け USB カメラを含め、カメラ、ライブ映像、双方向音声でこのモードに対応しています。1つのビルドで iPhone、iPad、Mac に対応します。
iOS 27 と macOS 27 に対応
KlugMedia は、iPhone、iPad、MacBook、Mac mini 上の iOS 27、iPadOS 27、macOS 27、そして引き続き iOS 17 でもテスト済みです。このリリースのハイライト:
- 強化された受信パス:iOS 27 のタイミングでも、ライブストリームの停止と再開をいつでも安全に行えます
- macOS 27 で安定したカメラプレビュー:Mac 上の iPad アプリで、カメラの開始・停止・切り替え中も安定します
- すべての Mac で正しい向き:内蔵カメラと USB カメラが、macOS 26 と 27 のプレビューと受信側で正立して表示されます
- Mac での高速なカメラ切り替え:FaceTime カメラと USB カメラの間を約1秒で切り替えられます
- ノートPCの Wi-Fi でもクリアな音声:適応型バッファが MacBook に特有の短い Wi-Fi バーストを吸収し、ネットワークが落ち着くと元の大きさに戻ります
はじめに
アプリケーション記述子に拡張機能を追加します:
<extensions>
<extensionID>com.klug.media</extensionID>
</extensions>
最初の起動の前に、カメラとマイクの権限をリクエストしてください。その後は拡張機能がキャプチャを管理しますので、AIR の Camera や Microphone を同時に開かないでください。
コード例
当社のアプリで毎日稼働している API から取り出した、5つの完全なフローです。すべての呼び出しは即座に返り、結果は StatusEvent として届きます。完全なドキュメント →
import com.klug.petcam.camera.CameraStreamANE; import flash.events.StatusEvent; var media:CameraStreamANE = CameraStreamANE.instance; media.addEventListener( StatusEvent.STATUS, onMedia ); if ( media.isSupported ) { // camera, H.264 encoder and RTMP in one native session media.startPublish( "rtmp://your.server:1935/live", "station-1", 640, 480, // size 12, 800, // fps, kbps 2, // keyframe every 2 s: fast start at the viewer CameraStreamANE.CAMERA_BACK ); } function onMedia( e:StatusEvent ):void { if ( e.code != CameraStreamANE.STATUS_EVENT ) return; if ( e.level == "connected" ) showLive(); else if ( e.level.indexOf( "failed:" ) == 0 ) retryLater( e.level.substr( 7 ) ); else if ( e.level.indexOf( "authError:" ) == 0 ) askForLogin(); }
// the video is decoded natively and drawn in a view above the stage (pixels) media.startReceive( "rtmp://your.server:1935/live", "station-1", 0, 80, stage.stageWidth, stage.stageHeight - 160, 0, CameraStreamANE.RECEIVE_FIT ); // or RECEIVE_CROPFILL media.addEventListener( StatusEvent.STATUS, function( e:StatusEvent ):void { if ( e.code == "receiveStatus" && e.level == "firstFrame" ) hideSpinner(); }); // rotation, a folding phone, fullscreen: move the native view with the stage stage.addEventListener( Event.RESIZE, function( e:Event ):void { media.setReceiveFrame( 0, 80, stage.stageWidth, stage.stageHeight - 160 ); });
import com.klug.petcam.audioecho.*; var voice:OpusVoiceBridge = new OpusVoiceBridge( new NativeOpusCodec(), 48000, 1, 960 ); var link:Red5OpusTransport; var nc:NetConnection = new NetConnection(); nc.addEventListener( NetStatusEvent.NET_STATUS, function( e:NetStatusEvent ):void { if ( e.info.code == "NetConnection.Connect.Success" ) startVoice(); }); nc.connect( "rtmp://your.server:1935/live" ); function startVoice():void { link = new Red5OpusTransport( nc ); link.addEventListener( OpusPacketEvent.PACKET, function( e:OpusPacketEvent ):void { voice.playRemoteOpus( e.packet ); // decode and play the other side }); link.start( "voice_me", "voice_other" ); // my stream, the other side's stream voice.setVoiceProcessing( true ); // hardware echo cancellation EchoCancellationANE.instance.setDuck( false ); // both sides may talk at once voice.start(); addEventListener( Event.ENTER_FRAME, sendVoice ); } function sendVoice( e:Event ):void { var packet:ByteArray; while ( ( packet = voice.readLocalOpus() ) != null ) // every finished 20 ms frame link.sendPacket( packet ); }
media.showPreview( 20, 100, 320, 240, CameraStreamANE.CAMERA_FRONT ); // aiming preview media.switchCamera(); // front / back, or built-in / USB camera on the Mac if ( media.hasTorch ) media.setTorch( true ); media.setCameraZoom( 4 ); // 0..8 = 1.0x..2.0x, the stream is zoomed too media.setVideoBitrate( 500 ); // change the bitrate while live media.addEventListener( StatusEvent.STATUS, function( e:StatusEvent ):void { if ( e.code == "snapshotStatus" && e.level.indexOf( "saved:" ) == 0 ) showPhoto( e.level.substr( 6 ) ); // absolute path of the JPEG }); media.captureSnapshot( 640, 480 ); media.hidePreview(); // native views sit above the stage: hide them before dialogs
function stopAll():void { removeEventListener( Event.ENTER_FRAME, sendVoice ); voice.stop(); link.stop(); media.stopReceive(); media.stopPublish(); // always stop before publishing the same name again } // safe at any moment, also while a stream is still connecting stopAll(); media.startPublish( "rtmp://your.server:1935/live", "station-1" );
API 概要
| isSupported | ネイティブメディアセッションが利用可能な環境で true になります。 |
| startPublish(…) | カメラを起動し、RTMP で H.264 を配信します:サイズ、fps、ビットレート、キーフレーム間隔、カメラ、任意の RTMP ログイン。 |
| stopPublish() | 配信を停止します。プレビューが表示中でない限り、カメラは解放されます。 |
| startReceive(…) | リモートストリームをステージ上の矩形内でネイティブ再生します:アスペクト比を維持、または回転付きのクロップ表示(Android では引き伸ばし)。 |
| setReceiveFrame(x, y, w, h) | 回転、折りたたみ式スマートフォン、全画面表示のために、ネイティブ映像を移動・リサイズします。 |
| stopReceive() | 再生を停止し、ネイティブビューを削除します。 |
| showPreview(…) / hidePreview() | ステージ上にネイティブのカメラプレビューを表示します。単独でも、ライブ配信と並べても使えます。 |
| switchCamera() | ストリーミング中に次のカメラへ切り替えます:iPhone と iPad では前面と背面、Mac では内蔵カメラと USB カメラ。 |
| setTorch(on) / hasTorch | ストリーミング中のカメラライトと、デバイスにライトがあるかどうか。 |
| setCameraZoom(step) | iOS と Android での実際のカメラズーム。0〜8 が 1.0x〜2.0x に対応し、ストリームもズームされます。 |
| setVideoBitrate(kbps) | ライブ中にエンコーダーのビットレートを変更します。 |
| captureSnapshot(w, h) | ライブのカメラ映像から JPEG を保存します。 |
| isiOSAppOnMac | iOS ビルドが Apple シリコン搭載 Mac 上で iPad アプリとして実行されている場合に true になります。 |
| OpusVoiceBridge | 双方向音声:開始、停止、エコーキャンセリング、スピーカー、ゲイン、再生バッファ。 |
| Red5OpusTransport | 既存の RTMP 接続上の2本の専用ストリームで Opus パケットを運びます。 |
ステータスイベント
| cameraStatus | connecting · connected · disconnected · failed:<reason> · authError:<reason> · newBitrate:<rate> · authSuccess (Android) |
| receiveStatus | playing · firstFrame · disconnected · streamEnded および failed:<reason>(iOS、macOS) |
| snapshotStatus | saved:<path> · failed:<reason> |
KlugMedia を購入
AIR アプリケーション1つ分の年間ライセンスで、12か月間のすべてのアップデートが含まれます。お支払い後、拡張機能、ActionScript ライブラリ、ドキュメントをメールでお送りします(通常は1営業日以内)。ライセンスと条件
動作要件
| AIR SDK | 51.x(HARMAN) |
| iOS | iOS 17 以降 |
| Android | Android 6.0(API 23)以降 |
| macOS | macOS 11 以降 |
| Mac 上の iPad アプリ | Apple silicon、macOS 26 以降 |
| 権限 | カメラとマイク |
| サーバー | 任意の RTMP サーバー。対応する Klug ストリーミングサーバーを推奨 |