Обзор
В AIR сейчас нет актуальной реализации потоковой передачи медиа в реальном времени. KlugMedia добавляет её как нативное расширение. Камера, микрофон, кодировщик и эхоподавление работают вместе в одной нативной сессии, а ActionScript только отправляет команды и получает статус.
Видео публикуется и принимается как H.264, голос — как Opus, оба по стандартному RTMP. Принятое видео рисуется нативно поверх сцены AIR, поэтому остаётся чётким и плавным даже на старых устройствах.
KlugMedia ежедневно используется в наших приложениях, нашем доказательстве работоспособности, в App Store, Google Play и на Mac.
Возможности
- Автономность: один ANE, без базовой библиотеки и зависимых ANE
- Захват с камеры и публикация H.264 в одной нативной сессии
- Нативный приём и отображение H.264 с сохранением пропорций, с обрезкой по размеру и поворотом
- Двусторонний голос Opus с аппаратным эхоподавлением, обе стороны говорят одновременно
- Нативный предпросмотр камеры, переключение камеры, фонарик и зум
- Снимки прямо из изображения камеры
- Запись MP4 живых потоков на стриминг-сервере Klug, изображение и звук
- Автоматическая адаптация битрейта в слабых сетях
- Адаптивный буфер голоса, который переживает сбои Wi-Fi и сам возвращается к малой задержке
- На Mac: переключение между встроенной камерой и USB-камерами примерно за секунду
- Стандартный RTMP, поэтому работает с обычными медиасерверами
Стриминг-сервер
KlugMedia поставляется с соответствующим серверным ПО, разработанным вместе с расширением и настроенным для приложений AIR: обработка потоков, надёжное переподключение, двусторонняя передача голоса, запись в MP4 и живой просмотр в веб-браузере.
Поддерживаемые платформы
Приложения iPad на Mac
На Mac с Apple silicon сборка iOS приложения AIR может работать как приложение iPad. KlugMedia поддерживает этот режим с камерой, живым видео и двусторонней голосовой связью, включая встроенную камеру FaceTime и внешние USB-камеры. Одна сборка подходит для iPhone, iPad и Mac.
Готово к iOS 27 и macOS 27
KlugMedia протестирован на iOS 27, iPadOS 27 и macOS 27, на iPhone, iPad, MacBook и Mac mini, а также на iOS 17. Главное в этом выпуске:
- Усиленный путь приёма: остановка и перезапуск прямого потока безопасны в любой момент, в том числе при таймингах iOS 27
- Стабильный предпросмотр камеры на macOS 27 для приложений iPad на Mac, пока камера запускается, останавливается или переключается
- Правильная ориентация на любом Mac: встроенные и USB-камеры стоят ровно в предпросмотре и у получателя, на macOS 26 и 27
- Быстрое переключение камеры на Mac между камерой FaceTime и USB-камерами, примерно за секунду
- Чистый голос по Wi-Fi ноутбука: адаптивный буфер поглощает короткие всплески Wi-Fi, типичные для MacBook, и уменьшается обратно, когда сеть спокойна
Начало работы
Добавьте расширение в дескриптор вашего приложения:
<extensions>
<extensionID>com.klug.media</extensionID>
</extensions>
Запросите разрешение на камеру и микрофон до первого запуска. После этого захват принадлежит расширению: не открывайте одновременно Camera или Microphone из AIR.
Примеры кода
Пять полных сценариев из API, который ежедневно работает в наших приложениях. Каждый вызов возвращается сразу; результаты приходят в виде 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(…) | Запускает камеру и публикует H.264 по RTMP: размер, fps, битрейт, интервал ключевых кадров, камера, необязательный логин RTMP. |
| stopPublish() | Останавливает публикацию; камера освобождается, если предпросмотр не показывается. |
| startReceive(…) | Воспроизводит удалённый поток нативно внутри прямоугольника сцены: вписывание по пропорциям или обрезка по размеру с поворотом (растягивание на Android). |
| setReceiveFrame(x, y, w, h) | Перемещает и меняет размер нативного видео — для поворота, складных телефонов и полноэкранного режима. |
| stopReceive() | Останавливает воспроизведение и убирает нативное представление. |
| showPreview(…) / hidePreview() | Нативный предпросмотр камеры поверх сцены — отдельно или рядом с живой публикацией. |
| switchCamera() | Переключает на следующую камеру во время трансляции: фронтальная и задняя на iPhone и iPad, встроенная и USB-камеры на Mac. |
| setTorch(on) / hasTorch | Подсветка камеры во время трансляции и наличие её на устройстве. |
| setCameraZoom(step) | Реальный зум камеры на iOS и Android, 0..8 для 1.0x–2.0x; поток тоже масштабируется. |
| setVideoBitrate(kbps) | Изменяет битрейт кодировщика в прямом эфире. |
| captureSnapshot(w, h) | Сохраняет JPEG из живого изображения камеры. |
| isiOSAppOnMac | True, когда сборка iOS работает как приложение iPad на Mac с Apple silicon. |
| OpusVoiceBridge | Двусторонний голос: запуск, остановка, эхоподавление, динамик, усиление, буфер воспроизведения. |
| Red5OpusTransport | Передаёт пакеты Opus по вашему соединению RTMP на двух выделенных потоках. |
События статуса
| 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 со всеми обновлениями в течение 12 месяцев. После оплаты вы получаете расширение, библиотеку ActionScript и документацию по e-mail, обычно в течение одного рабочего дня. Лицензия и условия
- Лицензия на одно приложение AIR
- Все обновления в течение 12 месяцев
- Без поддержки
- Лицензия на одно приложение AIR
- Все обновления в течение 12 месяцев
- Ответ по e-mail в течение 7 дней
- Лицензия на одно приложение AIR
- Все обновления в течение 12 месяцев
- Ответ по e-mail в течение 48 часов
Требования
| AIR SDK | 51.x (HARMAN) |
| iOS | iOS 17 или новее |
| Android | Android 6.0 (API 23) или новее |
| macOS | macOS 11 или новее |
| Приложения iPad на Mac | Apple silicon, macOS 26 или новее |
| Разрешения | Камера и микрофон |
| Сервер | Любой сервер RTMP; рекомендуется соответствующий стриминг-сервер Klug |