Oversigt
AIR har i øjeblikket ingen opdateret implementering til realtidsmedie-streaming. KlugMedia tilføjer den som en native udvidelse. Kamera, mikrofon, encoder og ekkodæmpning kører sammen i én native session, og ActionScript sender kun kommandoer og modtager status.
Video publiceres og modtages som H.264, stemme som Opus, begge over standard RTMP. Modtaget video tegnes natively oven på AIR-stagen, så den forbliver skarp og flydende, også på ældre enheder.
KlugMedia er i daglig brug i vores apps, vores proof of concept, på App Store, Google Play og Mac.
Funktioner
- Selvstændig: én ANE, intet kernebibliotek eller afhængige ANE'er at tilføje
- Kameraoptagelse og H.264-publicering i én native session
- Native H.264-modtagelse og -visning, med korrekt billedformat, crop-to-fill og rotation
- Tovejs Opus-stemme med hardwarebaseret ekkodæmpning, begge sider taler samtidig
- Native kameraforhåndsvisning, kameraskift, lommelygte og zoom
- Snapshots taget direkte fra kamerabilledet
- MP4-optagelse af live streams på Klug-streamingserveren, billede og lyd
- Automatisk tilpasning af bitrate på svage netværk
- Adaptiv stemmebuffer, der klarer Wi-Fi-udfald og vender tilbage til lav forsinkelse af sig selv
- På Mac: skift mellem det indbyggede kamera og USB-kameraer på omkring et sekund
- Standard RTMP, så det virker med almindelige medieservere
Streamingserver
KlugMedia leveres med tilhørende serversoftware, udviklet sammen med udvidelsen og tunet til AIR-apps: håndtering af streams, pålidelige genforbindelser, tovejs stemmerelay, MP4-optagelse og live-visning i en webbrowser.
Understøttede platforme
iPad-apps på Mac
På Mac'er med Apple silicon kan iOS-buildet af en AIR-app køre som en iPad-app. KlugMedia understøtter denne tilstand med kamera, live video og tovejsstemme, inklusive det indbyggede FaceTime-kamera og eksterne USB-kameraer. Ét build dækker iPhone, iPad og Mac.
Klar til iOS 27 og macOS 27
KlugMedia er testet på iOS 27, iPadOS 27 og macOS 27, på iPhone, iPad, MacBook og Mac mini, og stadig på iOS 17. Højdepunkter i denne udgave:
- Hærdet modtagesti: det er sikkert at stoppe og genstarte en live stream til enhver tid, også under timingen i iOS 27
- Stabil kameraforhåndsvisning på macOS 27 til iPad-apps på Mac, mens kameraet starter, stopper eller skifter
- Korrekt orientering på alle Mac'er: indbyggede kameraer og USB-kameraer står oprejst i forhåndsvisningen og hos modtageren, på macOS 26 og 27
- Hurtigt kameraskift på Mac mellem FaceTime-kameraet og USB-kameraer, på omkring et sekund
- Klar stemme på bærbares Wi-Fi: en adaptiv buffer absorberer de korte Wi-Fi-udbrud, der er typiske for MacBooks, og skrumper igen, når netværket er roligt
Kom i gang
Tilføj udvidelsen til din applikationsdescriptor:
<extensions>
<extensionID>com.klug.media</extensionID>
</extensions>
Anmod om kamera- og mikrofontilladelse før den første start. Udvidelsen ejer derefter optagelsen: åbn ikke et AIR-Camera eller -Microphone samtidig.
Kodeeksempler
Fem komplette flows, taget fra den API, der kører i vores apps hver dag. Hvert kald vender straks tilbage; resultater ankommer som StatusEvents. Fuld dokumentation →
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-oversigt
| isSupported | Sand, hvor den native mediesession er tilgængelig. |
| startPublish(…) | Starter kameraet og publicerer H.264 over RTMP: størrelse, fps, bitrate, keyframe-interval, kamera, valgfrit RTMP-login. |
| stopPublish() | Stopper publiceringen; kameraet frigives, medmindre forhåndsvisningen stadig vises. |
| startReceive(…) | Afspiller en fjernstream natively inde i et rektangel på stagen: aspect-fit eller crop-to-fill med rotation (stretch på Android). |
| setReceiveFrame(x, y, w, h) | Flytter og ændrer størrelsen på den native video, til rotation, foldbare telefoner og fuldskærm. |
| stopReceive() | Stopper afspilningen og fjerner den native visning. |
| showPreview(…) / hidePreview() | Native kameraforhåndsvisning over stagen, alene eller ved siden af en live publicering. |
| switchCamera() | Skifter til det næste kamera under streaming: for og bag på iPhone og iPad, indbyggede kameraer og USB-kameraer på Mac. |
| setTorch(on) / hasTorch | Kameralys under streaming, og om enheden har et. |
| setCameraZoom(step) | Ægte kamerazoom på iOS og Android, 0..8 for 1,0x til 2,0x; streamen zoomes også. |
| setVideoBitrate(kbps) | Ændrer encoderens bitrate under live. |
| captureSnapshot(w, h) | Gemmer et JPEG fra det levende kamerabillede. |
| isiOSAppOnMac | Sand, når iOS-buildet kører som iPad-app på en Mac med Apple silicon. |
| OpusVoiceBridge | Tovejsstemme: start, stop, ekkodæmpning, højttaler, gain, afspilningsbuffer. |
| Red5OpusTransport | Fører Opus-pakkerne over din RTMP-forbindelse på to dedikerede streams. |
Statushændelser
| cameraStatus | connecting · connected · disconnected · failed:<reason> · authError:<reason> · newBitrate:<rate> · authSuccess (Android) |
| receiveStatus | playing · firstFrame · disconnected · streamEnded og failed:<reason> (iOS, macOS) |
| snapshotStatus | saved:<path> · failed:<reason> |
Køb KlugMedia
En årlig licens til én AIR-applikation, med alle opdateringer i 12 måneder. Efter betaling modtager du udvidelsen, ActionScript-biblioteket og dokumentationen pr. e-mail, normalt inden for én hverdag. Licens og vilkår
- Licens til én AIR-applikation
- Alle opdateringer i 12 måneder
- Svar pr. e-mail inden for 7 dage
- Licens til én AIR-applikation
- Alle opdateringer i 12 måneder
- Svar pr. e-mail inden for 48 timer
Krav
| AIR SDK | 51.x (HARMAN) |
| iOS | iOS 17 eller nyere |
| Android | Android 6.0 (API 23) eller nyere |
| macOS | macOS 11 eller nyere |
| iPad-apps på Mac | Apple silicon, macOS 26 eller nyere |
| Tilladelser | Kamera og mikrofon |
| Server | Enhver RTMP-server; den tilhørende Klug-streamingserver anbefales |