live_media_stream_sdk 0.4.7
live_media_stream_sdk: ^0.4.7 copied to clipboard
Self-hosted Flutter live streaming with native Opus and VP8/VP9 encoding, WebM output, advanced camera/audio controls, and any binary WebSocket server.
live_media_stream_sdk #
English #
A Flutter SDK for building low-cost, self-hosted live audio and video applications without requiring a dedicated media server, RTMP service, or WebRTC SFU.
live_media_stream_sdk covers the complete mobile side of a broadcast:
camera and microphone capture, native encoding, packetization, WebM muxing,
optional simultaneous recording, WebSocket delivery, playback, media
inspection, audio routing, device selection, lifecycle, and live diagnostics.
The same encoded packets can be broadcast and recorded at the same time; a
second encoder is not started.
The SDK performs native Opus and VP8/VP9 encoding and produces either separate timestamped packets or one live WebM byte stream. Your backend can therefore be a regular binary-capable WebSocket server that authenticates connections and forwards frames to viewers or stores them. For a simple relay it does not need to decode, transcode, mix, or understand the codecs. The included transport handles binary framing, metadata, reconnect attempts, bounded backpressure, video-frame dropping under congestion, and keyframe resynchronization.
This makes the package suitable for private broadcasts, field reporting, education, internal company streams, intercom systems, event audio, remote observation, and applications that need to own their transport and infrastructure.
A specialized media server is not required for ingest. Your WebSocket backend and receiving clients must still implement authentication, room routing, access control, scaling, and playback/decoding appropriate to your application. The SDK does not provide a hosted relay service or viewer UI.
In practical terms, the package can publish microphone-only, camera-only, or
synchronized camera + microphone; use the included WebSocket transport or a
custom transport; save the same stream as a seekable .webm without double
encoding; export/share the completed file; and play local files, HTTP(S),
HLS/DASH, WebM, Ogg Opus, and SDK WebSocket streams.
Features #
Server and transport #
- No RTMP server, WebRTC SFU, MCU, or hosted media SDK is required for ingest.
- Works with a standard binary WebSocket endpoint.
- Transport-independent API for SignalR, native sockets, message brokers, or application-specific transports.
- One binary frame contains protocol version, JSON metadata, and raw media; payloads are not inflated with base64.
- Room, sender, receiver, user, command, and custom header metadata.
- Automatic reconnect attempts and a fresh keyframe request after reconnect.
- Incoming JSON control messages and binary messages are exposed to the app.
- Transport is optional: consume packets directly and send or store them yourself.
Camera and video #
- Native camera capture and hardware-supported VP8/VP9 encoding.
- Front and rear camera selection and live camera switching.
- Independent runtime camera enable/disable without stopping the stream.
- 480p, 720p, and 1080p profiles.
- Configurable frame rate and video bitrate.
- Runtime bitrate changes without restarting the stream.
- Manual keyframe requests for viewer synchronization and reconnect recovery.
- Audio-only, video-only, and combined audio/video operation.
- Per-packet timestamps, sequence numbers, dimensions, codec, rotation, camera position, bitrate, and keyframe metadata.
- Android and iOS Flutter texture preview with correct aspect ratio.
- iOS thermal protection reduces FPS/bitrate under heat, temporarily pauses video at critical temperature, and restores it with a keyframe after cooling.
Microphone and audio #
- Native Opus encoding with configurable bitrate.
- Independent runtime microphone enable/disable without stopping the stream.
- Mono/stereo selection and 8, 12, 16, 24, or 48 kHz sample rates, subject to device support.
- Built-in, wired headset, Bluetooth, and USB input discovery and selection.
- Deterministic microphone priority helper for field devices.
- Android software input gain from
0.0to8.0with clipping protection. - Android device-dependent noise suppression, automatic gain control, and acoustic echo cancellation.
- iOS preferred-input routing and voice-chat audio session mode when echo cancellation is requested.
- Android voice activity detection with configurable silence threshold and hangover; silent frames can be skipped before encoding.
- Live dB, peak, silence, clipping, audio-focus, and device-change events.
Output, lifecycle, and diagnostics #
- Independent
AudioPacketandVideoPacketoutput. - Ordered live
WebmPacketoutput containing VP8/VP9 and Opus. - Native WebM recording that reuses encoded data without a second encoder.
- Optional recording directory/name with safe defaults and collision handling.
- Rich recording result for upload workflows: path, size, duration, media format, counters, timestamps, and errors.
- Hybrid native player for platform-supported files/URLs/HLS plus the SDK's VP8/VP9 + Opus WebM fallback and WebM live streams.
- Ready-made audio/video controls with seek, time, mute, volume, speed, adaptive sizing, fullscreen, and orientation.
- Headless-friendly player state, timeline, capabilities, and custom Flutter control overlays for product-specific player experiences.
- Pause/resume and clean asynchronous shutdown with final muxer flushing.
- Android camera/microphone foreground service with customizable notification.
- iOS background audio; camera automatically stops in the background and resumes with a keyframe in the foreground.
- Live upload/encoding bitrate, FPS, CPU, memory, packet, dropped-frame, keyframe, audio-level, effect, and thermal statistics.
Platform matrix #
| Capability | Android | iOS |
|---|---|---|
| Opus audio and VP8/VP9 video | ✓ | ✓ |
| Audio-only, video-only, audio/video | ✓ | ✓ |
| Separate packet and live WebM output | ✓ | ✓ |
| Front/rear camera and runtime video controls | ✓ | ✓ |
| Microphone discovery and selection | ✓ | ✓ |
| Native Flutter texture preview | ✓ | ✓ |
| Camera/microphone foreground service | ✓ | — |
| Background audio with camera recovery | — | ✓ |
| Thermal video protection | — | ✓ |
| Native WebM recording and metadata | ✓ | ✓ |
| Native multi-format file/URL player | ✓ | ✓ |
| WebM byte-stream/WebSocket player | ✓ | ✓ |
| Swift Package Manager and CocoaPods | — | ✓ |
The iOS binary supports physical arm64 devices and both Apple Silicon
(arm64) and Intel (x86_64) simulators. Camera capture is unavailable when
the selected simulator has no camera input.
Codec and container support #
Encoding and decoding are different capabilities. The broadcast encoder is deliberately deterministic, while the player additionally uses the operating system's native media framework.
| Operation | Android | iOS |
|---|---|---|
| Audio encode | Opus | Opus (bundled libopus) |
| Video encode | VP8/VP9 through MediaCodec when a device encoder exists | VP8/VP9 through bundled libvpx |
| Live/recording container | WebM: Opus + VP8/VP9 | WebM: Opus + VP8/VP9 |
| SDK WebM decode | VP8, VP9, Opus through Media3/device codecs | VP8/VP9 through libvpx; Opus through libopus |
| Ogg audio decode | Opus, Vorbis, FLAC where Media3/device support exists | Opus through the SDK; other supported formats through AVFoundation |
| Common native audio decode | AAC, MP3, Opus, Vorbis, FLAC, AMR, PCM/WAV, subject to the device | AAC, HE-AAC, ALAC, MP3, PCM/WAV through AVFoundation; Opus through the SDK fallback |
| Common native video decode | H.264/AVC, H.265/HEVC, VP8, VP9, AV1, MPEG-4, MPEG-2 when a device decoder exists | H.264/AVC and H.265/HEVC through AVFoundation; VP8/VP9 through the SDK fallback |
| Containers/protocols | MP4/M4V, Matroska/WebM, Ogg, MPEG-TS/PS, FLV, ADTS, AMR, FLAC, WAV, HLS, DASH | MP4/M4V/MOV, M4A, MP3, WAV, HLS through AVFoundation; WebM and Ogg Opus through the SDK |
This matrix describes the implemented playback paths, not a guarantee that every profile plays on every device. Native playback depends on OS version, hardware decoders, codec profile/level, bit depth, DRM, container correctness, and server range/adaptive-streaming behavior. Raw headerless Opus is not a normal media file; use Ogg Opus or WebM.
Installation #
dependencies:
live_media_stream_sdk: ^0.4.7
Import the public API:
import 'package:live_media_stream_sdk/live_media_stream_sdk.dart';
The host application is responsible for requesting camera, microphone, and notification permissions before starting a stream.
Android #
The plugin manifest declares camera, microphone, foreground-service, and notification permissions. On Android 13 and later, request notification permission in the host app. Start a camera/microphone foreground service while the application is visible to satisfy Android's while-in-use restrictions.
iOS #
Add usage descriptions to the host application's Info.plist:
<key>NSCameraUsageDescription</key>
<string>Camera access is required for live video.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Microphone access is required for live audio.</string>
To continue audio while the app is in the background, also add:
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
iOS suspends camera capture in the background. The plugin keeps audio active and restarts video with a keyframe when the application returns to the foreground.
The plugin supports both Swift Package Manager and CocoaPods. New Flutter projects can use SwiftPM without an additional integration step; CocoaPods is retained for projects that have not migrated yet.
Quick start #
final controller = LiveMediaStreamController();
await controller.initialize();
final packetSubscription = controller.packets.listen((packet) {
// Store, forward, or inspect encoded media packets.
});
await controller.start(
const LiveStreamConfiguration(
mediaMode: StreamMediaMode.audioVideo,
videoCodec: VideoCodec.vp8,
audioCodec: AudioCodec.opus,
resolution: StreamResolution.p480,
framesPerSecond: 20,
videoBitrate: 800000,
audioBitrate: 64000,
),
);
// Later:
await controller.stop();
await packetSubscription.cancel();
await controller.dispose();
For long iOS sessions, VP8 at 480p, 15–20 FPS, and 600–900 Kbit/s is a good starting profile. The plugin automatically reduces video work under serious thermal pressure and temporarily switches to audio-only at the critical thermal state.
Usage guide #
The complete production sequence is:
- Request only the permissions needed by the selected media mode.
- Create and initialize one controller for the streaming screen.
- Subscribe to packets, events, and statistics before starting.
- Optionally create a transport and recording configuration.
- Start the stream and await every runtime control operation.
- Stop the stream and receive the finalized recording result.
- Export or share the recording when requested.
- Cancel subscriptions and dispose the controller.
1. Request permissions #
Request the permissions required by the selected mode before calling
start():
audioOnly: microphonevideoOnly: cameraaudioVideo: camera and microphone- Android 13+: notifications when the foreground-service notification is used
Permission prompts belong to the host application. Use a permission package of your choice or your existing native permission flow.
2. Create and initialize the controller #
Keep one controller for the lifetime of the streaming screen:
late final LiveMediaStreamController controller;
Future<void> initializeStreaming() async {
controller = LiveMediaStreamController();
await controller.initialize();
}
3. Listen to output and diagnostics #
Subscribe before starting so the first packets and events are not missed:
final packetSubscription = controller.packets.listen((packet) {
// AudioPacket, VideoPacket, or WebmPacket.
});
final eventSubscription = controller.events.listen((event) {
// Camera, audio, connection, state, and error events.
});
final statisticsSubscription = controller.statistics.listen((statistics) {
debugPrint(
'upload=${statistics.uploadBitsPerSecond}, '
'cpu=${statistics.cpuUsage}, '
'thermal=${statistics.thermalState}',
);
});
4. Configure and start the stream #
final configuration = LiveStreamConfiguration(
mediaMode: StreamMediaMode.audioVideo,
outputMode: StreamOutputMode.separateAudioVideo,
cameraPosition: CameraPosition.front,
resolution: StreamResolution.p480,
framesPerSecond: 20,
videoBitrate: 800000,
audioBitrate: 64000,
audioProcessing: const AudioProcessingConfiguration(
noiseSuppression: true,
automaticGainControl: true,
echoCancellation: true,
),
);
await controller.start(configuration);
Use separateAudioVideo when your server accepts independent audio and video
packets. Use webm when the receiver expects one playable WebM byte stream.
transport is optional. Without one, encoded output is still available from
controller.packets, allowing the application to store or send it itself.
5. Control an active stream #
await controller.switchCamera(CameraPosition.back);
await controller.setMicrophoneEnabled(false);
await controller.setCameraEnabled(false);
await controller.setMicrophoneEnabled(true);
await controller.setCameraEnabled(true);
await controller.setVideoBitrate(600000);
await controller.requestVideoKeyFrame();
await controller.pause();
await controller.resume();
Only call video controls while video is enabled. Await each operation before starting another state change.
6. Stop and clean up #
await controller.stop();
await packetSubscription.cancel();
await eventSubscription.cancel();
await statisticsSubscription.cancel();
await controller.dispose();
Always await stop() before closing your socket or leaving the streaming
screen. This allows buffered packets and final WebM bytes to be delivered.
When recording is enabled, stop() returns the finalized file:
final stopResult = await controller.stop();
final recording = stopResult.recording;
if (recording != null && recording.isCompleted) {
debugPrint(
'${recording.filePath} • ${recording.fileSizeKilobytes} KB '
'• ${recording.duration}',
);
// Native Files/Save picker; null means that the user cancelled.
final exportedUri = await controller.exportRecording(recording);
// Android Sharesheet / iOS share sheet: WhatsApp, AirDrop, etc.
await controller.shareRecording(recording);
}
The muxer first finalizes the recording in app-scoped storage. The native
export picker then lets the user select Documents, Downloads, iCloud Drive, or
another provider. The file name is optional and .webm is appended
automatically.
List and manage recordings retained in the SDK-managed directory:
final recordings = await controller.listRecordings();
await controller.exportStoredRecording(recordings.first);
await controller.shareStoredRecording(recordings.first);
await controller.deleteRecording(recordings.first);
Deletion is restricted to .webm files located directly inside the
SDK-managed recording directory. Arbitrary file paths cannot be deleted.
How the SDK fits into your system #
Camera + microphone
│
▼
Native capture → Opus + VP8/VP9 encoding
│
├── controller.packets ──→ your transport / WebSocket relay
│
├── native WebM recorder ──→ uploadable .webm file
│
└── Flutter texture ──→ local camera preview
Media file / URL / WebSocket WebM stream
│
▼
Native player → video texture + audio output → Flutter controls
Encoding and decoding run natively. The WebSocket server does not need to decode, transcode, mix, or understand WebM when it only forwards binary frames. This keeps the ingest architecture small, but it also means that authentication, authorization, rooms, slow-client handling, persistence, monitoring, and horizontal scaling remain backend responsibilities.
Complete configuration reference #
LiveStreamConfiguration #
| Property | Default | Meaning |
|---|---|---|
transport |
null |
Optional delivery layer. With null, encoded packets are available only from controller.packets; useful for local recording and a custom backend. |
socketMessageBuilder |
SDK builder | Converts packets to your application’s socket envelope and metadata. |
mediaMode |
audioVideo |
audioOnly, videoOnly, or audioVideo; also determines required permissions. |
videoCodec |
vp8 |
VP8 or VP9. VP8 is the safest compatibility and heat starting point. |
audioCodec |
opus |
Native Opus audio encoding. |
resolution |
p720 |
p480, p720, or p1080. Higher values increase heat, bitrate, and battery use. |
outputMode |
separateAudioVideo |
Emits independent encoded packets or ordered live WebM chunks. |
cameraPosition |
back |
Initial front or rear camera. |
qualityMode |
automatic |
Chooses the SDK quality strategy. Automatic mode permits thermal adaptation on iOS. |
framesPerSecond |
30 |
Requested video FPS. Use 15–20 for long, heat-sensitive broadcasts. |
videoBitrate |
1500000 |
Video bits per second. Can be changed during a stream. |
audioBitrate |
64000 |
Opus bits per second. 32–64 Kbit/s is a practical voice range. |
audioProcessing |
defaults | Input device, gain, channel/sample rate, AGC, AEC, noise suppression, and VAD. |
recording |
null |
Enables native WebM recording when provided and enabled. |
enableForegroundService |
true |
Keeps Android capture represented by the required foreground notification. |
foregroundNotificationTitle/Text |
localized defaults | Android notification content. |
overlays |
empty | Metadata/configuration for supported media overlays. |
AudioProcessingConfiguration #
| Property | Default | Meaning |
|---|---|---|
automaticGainControl |
true |
Requests native AGC where the device supports it. |
echoCancellation |
true |
Requests AEC/voice-chat routing; actual availability is device-dependent. |
noiseSuppression |
true |
Requests native noise suppression. |
voiceActivityDetection |
false |
Detects silence; Android can skip silent frames before encoding. |
inputDevice |
default input | Select a value returned by getAudioInputDevices(). |
gain |
1.0 |
Android software gain from 0.0 to 8.0; watch clipping events above 1.0. |
channelMode |
mono |
Mono or stereo, subject to route support. |
sampleRate |
48000 |
8, 12, 16, 24, or 48 kHz. Opus commonly performs best at 48 kHz. |
silenceThresholdDb |
-50 |
VAD level below which input is considered silent. |
vadHangoverMilliseconds |
250 |
Keeps speech active briefly after the level falls below the threshold. |
Native audio effects are requests, not guarantees. Read
noiseSuppressorEnabled, automaticGainControlEnabled, and
echoCancelerEnabled from live statistics to learn what the current device
actually enabled.
Controller operations and when to use them #
| API | Use |
|---|---|
initialize() |
Once before accessing devices or starting the first stream. |
start(configuration) |
Starts capture, encoders, optional transport, recorder, and foreground lifecycle. |
pause() / resume() |
Temporarily suspend and continue the active publishing session. |
stop() |
Flushes encoders/muxer and returns LiveStreamStopResult; always await it. |
dispose() |
Releases channels and controller resources when the screen/service is permanently finished. |
getAudioInputDevices() |
Lists built-in, wired, Bluetooth, and USB inputs. |
switchCamera(position) |
Changes front/rear camera during video streaming. |
setMicrophoneEnabled(enabled) |
Enables or disables microphone packets while the stream remains active. |
setCameraEnabled(enabled) |
Enables or disables camera packets while the stream remains active; re-enabling requests a keyframe. |
setVideoBitrate(value) |
Adapts bandwidth/heat without restarting. |
requestVideoKeyFrame() |
Resynchronizes new viewers or viewers recovering after reconnect. |
getVideoTextureId() |
Obtains the local native camera preview texture where supported. |
listRecordings() |
Lists finalized WebM files retained in the SDK-managed directory. |
deleteRecording(recording) |
Deletes one SDK-managed recording. |
exportStoredRecording(recording) |
Opens the native save picker for a listed recording. |
shareStoredRecording(recording) |
Opens the native share sheet for a listed recording. |
Subscribe to packets, events, and statistics before start() so the
first data is not missed. Cancel those subscriptions after stop() and before
disposing the owning screen.
Choosing an output and server design #
- Choose
separateAudioVideowhen your protocol routes audio and video independently or needs packet-level metadata. - Choose
webmwhen receivers want an ordered, directly demuxable live WebM byte stream. - Enable
recordingindependently of either output. Recording reuses encoded data and does not require network transport. - Keep
transport: nullfor offline recording or when your app consumescontroller.packetsand sends them through an existing SignalR, broker, or native socket layer. - Use
WebSocketLiveMediaTransportwhen a normal binary WebSocket relay is sufficient.
SDK-framed WebSocket messages contain a protocol header, JSON metadata, and the raw binary media payload. Do not base64 the payload. Preserve WebM chunk order, apply backpressure, and request/send a fresh keyframe after a viewer joins or reconnects.
Error handling and production rules #
try {
await controller.start(configuration);
} on PlatformException catch (error) {
// Permission, camera, microphone, encoder, path, or native lifecycle error.
log('${error.code}: ${error.message}');
}
final events = controller.events.listen((event) {
if (event is LiveStreamError) {
log('${event.code}: ${event.message}');
}
});
- Never start two sessions with the same controller.
- Disable UI configuration controls while a session is starting or active.
- Await every lifecycle operation; do not call start/stop concurrently.
- Use writable app-scoped paths unless the user explicitly grants access to another directory.
- Use
wss://, authenticate the connection, authorize room membership, limit payload/rate, and never trust client metadata. - Monitor dropped frames, CPU, memory, thermal state, upload bitrate, and reconnects in production.
- A WebSocket relay is deliberately not a transcoder, DVR, CDN, SFU, or access control service. Add those components only if your product needs them.
WebSocket transport #
The SDK is transport-independent. Use WebSocketLiveMediaTransport or
implement LiveMediaTransport for SignalR, a native socket, or another
protocol:
final transport = WebSocketLiveMediaTransport(
uri: Uri.parse('wss://your-server.example/live'),
);
await controller.start(
LiveStreamConfiguration(
transport: transport,
socketMessageBuilder: (packet) => LiveSocketMessage.fromMediaPacket(
packet,
action: 'media',
command: 'publish',
groupName: 'room-42',
senderConnectionId: 'connection-id',
userId: 15,
userType: 'publisher',
),
),
);
The included transport sends JSON metadata and the raw binary payload in one binary frame, without base64 expansion.
Binary frame format #
WebSocketLiveMediaTransport encodes each LiveSocketMessage using the
following wire format:
| Offset | Size | Content |
|---|---|---|
0 |
1 byte | Protocol version. Currently 1. |
1 |
4 bytes | JSON metadata length as an unsigned 32-bit big-endian integer. |
5 |
Metadata length | UTF-8 encoded JSON metadata. |
5 + metadata length |
Remaining bytes | Raw binary media payload. Empty when the message has no payload. |
In other words:
+-----------+----------------------+---------------------+--------------------+
| version | metadata length | JSON metadata | raw media payload |
| 1 byte | 4 bytes, big-endian | UTF-8, variable | remaining bytes |
+-----------+----------------------+---------------------+--------------------+
The JSON metadata represents LiveSocketMessage and can contain:
| Field | Type | Description |
|---|---|---|
action |
string | Required message action, such as media. |
command |
string or null | Optional application command, such as publish. |
groupName |
string or null | Optional room or group identifier. |
receiverConnectionId |
string or null | Optional target connection identifier. |
senderConnectionId |
string or null | Optional sender connection identifier. |
userId |
integer or null | Optional application user identifier. |
userType |
string or null | Optional application-specific user type. |
headers |
JSON object or null | Custom headers and media metadata. |
payloadLength |
integer | Number of raw payload bytes following the JSON metadata. |
Messages created with LiveSocketMessage.fromMediaPacket() add these values
to headers: mediaType, sequenceNumber, timestampUs, and
payloadLength. Audio packets additionally include codec, sampleRate,
channels, and bitrate; video packets include codec, width, height,
and isKeyFrame. mediaType is audio, video, or webm.
JSON-only control messages are also accepted. A text frame, or a UTF-8 binary
frame whose first byte is { (0x7B), is decoded directly as
LiveSocketMessage metadata and has no binary payload. Servers that only
relay media can forward SDK-framed binary messages unchanged. Servers that
inspect or route messages should validate the version, metadata length,
declared payload length, and application-specific authorization before using
the contents.
Minimal WebSocket relay #
The server does not have to decode the media when its only job is to relay frames. For example, this minimal Node.js server broadcasts each incoming binary frame to the other connections:
npm install ws
import { WebSocketServer } from 'ws';
const server = new WebSocketServer({ port: 8080 });
server.on('connection', (sender) => {
sender.on('message', (data, isBinary) => {
for (const viewer of server.clients) {
if (viewer !== sender && viewer.readyState === 1) {
viewer.send(data, { binary: isBinary });
}
}
});
});
This is intentionally a minimal architecture example. Add TLS (wss://),
authentication, rooms, authorization, rate limits, backpressure, monitoring,
and horizontal fan-out before production use. Receiving applications can use
WebSocketLiveMediaTransport.decode() to recover metadata and payload.
WebM output #
await controller.start(
const LiveStreamConfiguration(
outputMode: StreamOutputMode.webm,
videoCodec: VideoCodec.vp8,
audioCodec: AudioCodec.opus,
),
);
In WebM mode, controller.packets emits ordered WebmPacket chunks. Concatenate
them without reordering. Always await controller.stop() so the final muxer
bytes are emitted before disconnecting the transport.
Native WebM recording #
Recording uses the same encoded Opus and VP8/VP9 packets as the live stream, so enabling it does not start a second camera, microphone, or encoder. It works with audio-only, video-only, and combined sessions.
await controller.start(
const LiveStreamConfiguration(
mediaMode: StreamMediaMode.audioVideo,
recording: RecordingConfiguration(
// Both values are optional.
directoryPath: '/a/writable/directory',
fileName: 'training-session.webm',
),
),
);
final stopResult = await controller.stop();
final recording = stopResult.recording;
if (recording?.isSuccessful == true) {
print(recording!.filePath); // Upload/share this file.
print(recording.fileSizeBytes);
print(recording.fileSizeKilobytes);
print(recording.duration);
print('${recording.width}x${recording.height}');
}
While recording, LiveStreamStatistics.isRecording and
recordingDuration provide the live elapsed time. After stop(), the
authoritative finalized duration remains available as
MediaRecordingResult.duration.
The recording is finalized in app-scoped storage first. Then let the user pick a visible destination or open the native share sheet:
final recording = (await controller.stop()).recording;
if (recording?.isSuccessful == true) {
// Android: system Save/Storage Access Framework.
// iOS: Files picker (On My iPhone, iCloud Drive, providers, etc.).
final exportedUri = await controller.exportRecording(recording!);
// WhatsApp, Telegram, AirDrop, Drive, Mail and other installed targets.
await controller.shareRecording(recording);
}
Users provide only a file name; .webm is appended automatically. Direct
arbitrary filesystem paths remain available for advanced app-controlled
storage, but user-facing applications should prefer the system picker because
Android scoped storage and the iOS sandbox do not expose app paths as normal
shared folders.
If directoryPath is omitted, Android uses the application's external files
directory (Android/data/<package>/files) and iOS uses its Documents
directory. If fileName is omitted, a unique .webm name is generated. An
existing file is never replaced unless overwriteExisting is explicitly
enabled. MediaRecordingResult also reports codecs, bitrates, sample rate,
channel count, FPS, packet/frame/drop counts, keyframes, rotation, timestamps,
and any recording error.
Recording result reference #
controller.stop() returns LiveStreamStopResult. Its recording value is
null when recording was not requested. Otherwise inspect:
| Field | Meaning |
|---|---|
status / isSuccessful |
Completed or failed final state. |
filePath, directoryPath, fileName |
Resolved upload/share destination. |
fileSizeBytes, fileSizeKilobytes, fileSizeMegabytes |
Exact and derived file sizes. |
duration, startedAt, endedAt |
Media duration and UTC session timestamps. |
mediaMode, hasAudio, hasVideo |
Recorded stream composition. |
audioCodec, videoCodec, bitrates |
Codec and configured rate metadata. |
sampleRate, channels |
Audio format. |
width, height, framesPerSecond, rotationDegrees, aspectRatio |
Video/display format. |
| packet/frame/drop/keyframe counters | Diagnostics useful before upload. |
errorCode, errorMessage |
Native recording failure details. |
Do not upload until stop() completes. A path returned while capture is still
running may refer to a file whose muxer has not been finalized.
Recording and socket delivery are independent pipelines. A slow socket cannot
block native file writing: transport work is scheduled fairly with a bounded
queue, audio/keyframe priority, and keyframe recovery after disposable video
frames are dropped. If an ordered WebM socket cannot be reduced safely, socket
delivery stops with a transport_backpressure event while recording continues.
Audio and video playback #
The same controller plays platform-supported local media files and HTTP(S) URLs. WebM is supported on both platforms even where the system player does not support it. Raw byte streams and WebSocket live streams use the SDK's ordered WebM live protocol:
final player = LiveMediaPlayerController();
// Local file: MP4, MOV, M4A, MP3, Ogg Opus (.opus), WebM, etc.
await player.open(
const MediaFileSource('/path/to/video.mp4'),
autoplay: true,
);
// URL:
await player.open(
MediaUrlSource.parse(
'example.com/master.m3u8',
headers: const {'Authorization': 'Bearer token'},
),
);
// A WebSocket may send raw WebM binary chunks or the SDK binary frame format.
await player.open(
WebSocketMediaSource(Uri.parse('wss://example.com/watch/room-42')),
autoplay: true,
);
MediaUrlSource.parse accepts user-entered addresses with or without a
scheme. It adds https:// when the scheme is omitted and preserves an
explicit http:// or https://. The MediaUrlSource(Uri(...)) constructor is
also normalized by the controller before opening.
Use the ready-made video widget in a screen:
LiveMediaVideoPlayer(
controller: player,
// When omitted, the video's own 4:3, 16:9, 9:16, etc. ratio is used.
scale: 0.9,
fit: BoxFit.contain,
fullscreenConfiguration: const FullscreenConfiguration(
orientation: FullscreenOrientation.landscape,
),
)
Fully custom controls #
The rendering surface, media state, and controls are independent. There are three integration levels:
- Use
LiveMediaVideoPlayeras-is for the SDK control overlay. - Pass
controlsBuilderto replace that overlay with application-owned controls. Custom controls remain visible and own their gestures; the SDK does not auto-hide them or consume surface taps. - Use
LiveMediaVideoSurfacedirectly for a completely external layout. SetshowControls: falseonLiveMediaVideoPlayerwhen no overlay is wanted.
LiveMediaPlayerController is a ValueListenable<LiveMediaPlayerState> and
also exposes a broadcast states stream. A custom progress bar can therefore
rebuild without depending on ready-made widgets:
LiveMediaVideoPlayer(
controller: player,
controlsBuilder: (context, controller, state) {
final timeline = state.timeline;
return Align(
alignment: Alignment.bottomCenter,
child: Slider(
value: (timeline.progress ?? 0) * 100,
max: 100,
onChanged: state.capabilities.canSeek && timeline.hasDuration
? (value) => controller.seekTo(
timeline.duration * (value / 100),
)
: null,
),
);
},
)
Use state.timeline (also available as controller.timeline) for position,
buffered position, duration, live/seekability flags, optional seekable bounds,
and a progress value when duration is finite. progress is null for a
live or otherwise unbounded timeline; do not represent those sources with a
fake fixed duration.
Use state.capabilities (also controller.capabilities) before displaying an
action. It exposes canPlay, canPause, canSeek, canSetVolume, canMute,
canChangePlaybackSpeed, canChangeAudioOutput, and canGoLive. The
controller provides play, pause, stop, seek/seekTo, seekBy,
togglePlayback, setVolume, setMuted, setPlaybackSpeed/
setPlaybackRate, setAudioOutput, and goLive.
For completed seekable audio or video, play() automatically seeks to the
start before resuming. This applies equally to the SDK controls and a custom
play button; live sources retain their existing behavior.
For an external layout, render LiveMediaVideoSurface(controller: player) and
listen with ValueListenableBuilder<LiveMediaPlayerState> or player.states.
This supports custom overlays such as a branded progress bar, chapter markers,
large accessibility controls, a live-edge button, playback analytics, or an
entirely different audio/video experience.
Alternatively set width and/or height instead of scale. The video widget
includes play/pause, seekable progress, elapsed/total time, mute, volume,
0.25x–3x speed, fullscreen, and portrait/landscape support. For audio-only
media use LiveMediaAudioPlayer(controller: player), which provides the
relevant controls without a video surface. Live WebSocket sources are not
seekable; recorded files and seekable URLs expose progress and seeking.
Select the physical audio route after opening a source:
await player.setAudioOutput(PlayerAudioOutput.speaker);
await player.setAudioOutput(PlayerAudioOutput.receiver);
await player.setAudioOutput(PlayerAudioOutput.systemDefault);
Receiver routing requires a phone with a built-in earpiece. Wired, Bluetooth,
AirPlay, and other external routes remain under the operating system's route
policy when systemDefault is selected.
Call player.dispose() when the owning screen is disposed. The current native
backend supports one active playback session per Flutter engine.
Format selection and native fallback #
The player does not reject a file merely because it is not WebM:
| Source | Android | iOS |
|---|---|---|
| Local file / HTTP(S) URL | Media3 extractors plus device decoders | AVPlayer/AVFoundation |
| HLS | Media3 HLS | AVPlayer |
| MPEG-DASH | Media3 DASH | Device/AVFoundation support; HLS is the recommended Apple adaptive format |
| WebM VP8/VP9 + Opus file/URL | Media3 | SDK WebM demuxer + libvpx + Opus |
Ogg Opus .opus / .ogg / .oga file/URL |
Media3 | SDK Ogg demuxer + Opus |
| WebSocket / byte stream | SDK WebM live protocol | SDK WebM live protocol |
On iOS, the SDK reads the Opus channel mapping from OpusHead. Multichannel
WebM/Ogg Opus sources, including 5.1 media, use the libopus multistream decoder
and are downmixed to stereo for playback.
Typical native file support includes MP4/M4V/MOV with H.264/HEVC and AAC, M4A/AAC, MP3, and WAV/PCM, subject to OS version, device decoder capability, profile, level, bit depth, DRM, and the actual container/track combination. Android Media3 additionally recognizes formats such as Matroska/WebM, Ogg, FLV, MPEG-TS, HLS, and DASH when the contained codec is available.
An .opus file is expected to contain Opus packets in an Ogg container, as
defined for normal Ogg Opus files. Headerless raw Opus packets are not a
self-describing, seekable media file and must first be placed in Ogg or WebM.
An extension is only a hint. The native backend inspects the media tracks and reports the discovered container/codec. A valid container can still fail when its codec profile is unsupported, encrypted/DRM content lacks a license session, the server does not support required range/adaptive requests, or the file is incomplete.
WebSocket is intentionally narrower: arbitrary chunks of a normal MP4/MOV file do not automatically form a playable live stream. The current SDK live protocol transports ordered WebM chunks. Supporting fMP4 or MPEG-TS over a custom WebSocket requires an explicit stream type and a matching incremental parser.
Player controller reference #
| API/source | Meaning |
|---|---|
MediaFileSource(path) |
App-readable local media file; native backend is selected automatically. |
MediaUrlSource(uri, headers:) |
HTTP(S) media or adaptive manifest; a missing scheme is normalized to HTTPS. |
MediaUrlSource.parse(text, headers:) |
Recommended for text input; accepts example.com/media.mp3 without requiring https://. |
WebSocketMediaSource(uri, protocols:) |
Raw WebM chunks or SDK-framed WebM binary messages. |
MediaByteStreamSource(stream) |
Ordered WebM bytes produced by application code. |
open(source, autoplay:) |
Closes the previous source, prepares native decoding, and returns discovered MediaInfo. |
play(), pause(), stop() |
Playback lifecycle. |
seek(position) |
Recorded/seekable sources only. |
seekTo(position), seekBy(offset) |
Custom-control aliases for absolute and relative seeking. |
togglePlayback() |
Plays or pauses based on the current state. |
setVolume(0…1), setMuted() |
Audio output controls. |
setPlaybackSpeed(0.25…3), setPlaybackRate() |
Audio and video playback rate. |
goLive() |
Returns a live source to its latest buffered position. |
timeline, capabilities, states |
Timeline snapshot, available actions, and a stream of state updates for custom controls. |
LiveMediaVideoSurface |
Video texture only; use it to compose a fully external player layout. |
LiveMediaVideoPlayer.controlsBuilder |
Replaces the default in-video control overlay. |
controller.state reports opening, ready, playing, paused, buffering,
completed, failed, and disposed states along with position, buffered position,
duration, volume, speed, texture ID, metadata, and error details.
Sizing rules for LiveMediaVideoPlayer:
- With no size arguments it follows the discovered video aspect ratio.
widthonly derives height from the ratio;heightonly derives width.widthandheighttogether fit the player into that exact design box.scalescales relative to the available parent width.- Do not combine
scalewith explicit width/height. BoxFit.containpreserves the whole frame;coverfills and crops.- Fullscreen orientation can be automatic, portrait, landscape, or all.
Camera preview #
final textureId = await controller.getVideoTextureId();
Widget preview = textureId == null
? const SizedBox.shrink()
: LiveMediaPreview(textureId: textureId);
The same public texture preview API is available on Android and iOS. Request the texture ID once after controller initialization; frames begin when a video stream starts and stop when the stream ends.
Audio processing and statistics #
AudioProcessingConfiguration controls gain, input selection, channel mode,
sample rate, echo cancellation, noise suppression, automatic gain control,
voice activity detection, and silence thresholds. See
AUDIO_FEATURES.md for platform-specific details.
Listen to controller.events and controller.statistics for audio levels,
stream state, encoded/upload bitrates, CPU use, active audio effects, and iOS
thermal state.
Example #
The example application is an interactive learning lab. It supports local
capture without a server, every stream/audio/video setting, native recording
configuration, rich completion metadata, live diagnostics, and playback of
the last recording, another file, an HTTP(S) URL, or a WebSocket stream.
Run it on a physical device:
cd example
flutter pub get
flutter run
Start with WebSocket delivery disabled to verify capture and recording. Then
enable it in the example UI, enter an IP address or WebSocket URL, and test
your relay. The example normalizes local IP addresses to ws://, domains to
wss://, and demonstrates simultaneous recording and socket delivery.
License #
This package is available under the MIT License. The bundled iOS libvpx and
libopus binaries have separate terms documented in
THIRD_PARTY_NOTICES.md.
Türkçe #
Özel bir media server, RTMP servisi veya WebRTC SFU kurmak zorunda kalmadan düşük maliyetli ve tamamen size ait canlı ses/video uygulamaları geliştirmenizi sağlayan bir Flutter SDK'sıdır.
live_media_stream_sdk, yayının mobil taraftaki bütün zincirini kapsar:
kamera/mikrofon yakalama, native encode, paketleme, WebM mux, yayınla eşzamanlı
kayıt, WebSocket aktarımı, player/decode, medya bilgisi okuma, ses çıkış
yönlendirmesi, cihaz seçimi, yaşam döngüsü ve canlı ölçümler. Yayına gönderilen
encode edilmiş paketler aynı anda kayda yazılır; ikinci encoder çalıştırılmaz.
SDK, Opus sesi ve VP8/VP9 videoyu native olarak encode ederek ayrı timestamp'li paketler veya tek bir canlı WebM byte akışı üretir. Backend tarafında binary frame kabul eden normal bir WebSocket sunucusu yeterlidir. Basit relay senaryosunda sunucunun codec çözmesi, transcode etmesi veya sesi/görüntüyü işlemesi gerekmez. Hazır taşıyıcı; binary frame formatını, metadata'yı, yeniden bağlantıyı, sınırlı kuyruk/backpressure yönetimini, yoğunlukta video karesi düşürmeyi ve keyframe ile yeniden senkronizasyonu yönetir.
Özel yayınlar, saha haberciliği, eğitim, şirket içi yayınlar, interkom sistemleri, etkinlik sesi, uzaktan gözlem ve kendi altyapısına sahip olmak isteyen uygulamalar için uygundur.
Yayın verisini almak için özel bir media server gerekmez. Ancak WebSocket sunucunuz ve izleyici uygulamalarınız; kimlik doğrulama, oda yönlendirme, erişim kontrolü, ölçekleme ve oynatma/decode işlemlerini kendi ihtiyacınıza göre gerçekleştirmelidir. SDK hazır bir bulut relay servisi veya izleyici arayüzü sunmaz.
Paket; yalnız ses, yalnız video veya senkron ses + video yayınlamayı; hazır ya
da özel transport kullanmayı; aynı yayını ikinci kez encode etmeden .webm
olarak kaydetmeyi; dosyayı dışa aktarıp paylaşmayı; yerel dosya, HTTP(S),
HLS/DASH, WebM, Ogg Opus ve SDK WebSocket yayınlarını oynatmayı sağlar.
Özellikler #
Sunucu ve taşıma katmanı
- Yayın verisini almak için RTMP server, WebRTC SFU/MCU veya ücretli bir medya SDK'sı gerekmez.
- Standart, binary mesaj destekleyen herhangi bir WebSocket endpoint'iyle çalışır.
- SignalR, native socket, mesaj kuyruğu veya özel protokoller için transport-bağımsız API sunar.
- Her binary frame; protokol sürümü, JSON metadata ve ham medya payload'ını içerir. Base64 kullanılmadığı için veri gereksiz büyümez.
- Oda/grup, gönderen, alıcı, kullanıcı, komut ve uygulamaya özel header metadata'sı taşınabilir.
- Bağlantı koptuğunda otomatik yeniden bağlanma denenir; bağlantı geldiğinde izleyicileri tekrar senkronlamak için yeni keyframe istenir.
- Sunucudan gelen JSON kontrol mesajları ve binary mesajlar uygulamaya aktarılır.
- Hazır transport'u kullanmak zorunlu değildir; paketleri doğrudan dinleyip kendi bağlantınızla gönderebilir veya kaydedebilirsiniz.
Kamera ve video
- Native kamera yakalama ve cihazın desteklediği donanım hızlandırmalı VP8/VP9 kodlama.
- Ön/arka kamera seçimi ve yayın sırasında kamera değiştirme.
- 480p, 720p ve 1080p çözünürlük profilleri.
- Ayarlanabilir FPS ve video bitrate.
- Yayını kapatmadan çalışma anında bitrate değiştirme.
- İzleyici senkronizasyonu ve yeniden bağlantı için elle keyframe isteme.
- Yalnızca ses, yalnızca video veya birlikte ses/video yayını.
- Her video paketinde sıra numarası, timestamp, çözünürlük, codec, dönüş açısı, kamera yönü, bitrate ve keyframe bilgisi.
- Android ve iOS'ta doğru en-boy oranıyla Flutter texture kamera önizlemesi.
- iOS termal koruma: cihaz ısındığında FPS/bitrate düşürme, kritik sıcaklıkta videoyu geçici durdurma ve soğuyunca keyframe ile otomatik devam etme.
Mikrofon ve ses
- Native Opus kodlama ve ayarlanabilir ses bitrate.
- Cihaz desteğine bağlı mono/stereo ile 8, 12, 16, 24 veya 48 kHz örnekleme.
- Dahili mikrofon, kablolu kulaklık, Bluetooth ve USB girişlerini listeleme ve seçme.
- Saha cihazları için USB → kablolu → Bluetooth → dahili mikrofon şeklinde değiştirilebilir öncelikli seçim yardımcısı.
- Android'de
0.0–8.0aralığında yazılımsal gain ve clipping koruması. - Android'de cihaz desteğine bağlı gürültü azaltma, otomatik kazanç ve akustik yankı giderme.
- iOS'ta tercih edilen giriş cihazına yönlendirme ve yankı giderme istendiğinde voice-chat ses oturumu.
- Android'de ayarlanabilir sessizlik eşiği ve hangover süresiyle VAD; sessiz frame'ler kodlama öncesinde atlanabilir.
- Canlı dB, peak, sessizlik, clipping, audio focus ve cihaz değişikliği olayları.
Çıktı, yaşam döngüsü ve ölçümler
- Bağımsız
AudioPacketveVideoPacketçıkışı. - VP8/VP9 ve Opus içeren sıralı canlı
WebmPacketçıkışı. - İkinci encoder açmadan mevcut encode verisini kullanan native WebM kayıt.
- Opsiyonel kayıt yolu/adı, güvenli varsayılanlar ve isim çakışması yönetimi.
- Upload için dosya yolu, boyut, süre, format, sayaç, zaman ve hata metadata'sı.
- Platformun desteklediği dosya/URL/HLS formatları, WebM fallback ve WebM canlı akış için hibrit native player.
- Seek, süre, mute, volume, hız, otomatik boyut, fullscreen ve yön kontrolleri.
- Ürüne özel player deneyimleri için headless uyumlu state, timeline, capabilities ve özelleştirilebilir Flutter kontrol katmanı.
- Duraklatma/devam ettirme ve muxer'ın son verilerini gönderen güvenli kapatma.
- Android'de metni değiştirilebilir kamera/mikrofon foreground service bildirimi.
- iOS'ta arka planda ses; kamera arka planda durur ve öne dönünce keyframe ile otomatik açılır.
- Upload/kodlama bitrate, FPS, CPU, bellek, paket, düşen frame, keyframe, ses seviyesi, etkin ses efektleri ve termal durum istatistikleri.
Platform karşılaştırması
| Özellik | Android | iOS |
|---|---|---|
| Opus ses ve VP8/VP9 video | ✓ | ✓ |
| Yalnızca ses, yalnızca video, ses/video | ✓ | ✓ |
| Ayrı paket ve canlı WebM çıkışı | ✓ | ✓ |
| Ön/arka kamera ve çalışma anında video kontrolü | ✓ | ✓ |
| Mikrofon listeleme ve seçme | ✓ | ✓ |
| Native Flutter texture önizlemesi | ✓ | ✓ |
| Kamera/mikrofon foreground service | ✓ | — |
| Arka plan sesi ve kamera geri yükleme | — | ✓ |
| Termal video koruması | — | ✓ |
| Native WebM kayıt ve metadata | ✓ | ✓ |
| Native çok-formatlı dosya/URL player | ✓ | ✓ |
| WebM byte-stream/WebSocket player | ✓ | ✓ |
| Swift Package Manager ve CocoaPods | — | ✓ |
iOS ikilisi fiziksel arm64 cihazları, Apple Silicon (arm64) ve Intel
(x86_64) simülatörleri destekler. Seçilen simülatörde kamera girişi yoksa
kamera yayını kullanılamaz.
Codec ve container desteği
Encode ve decode iki ayrı yetenektir. Yayın encoder’ı kontrollü bir format üretir; player bunlara ek olarak işletim sisteminin native medya altyapısını kullanır.
| İşlem | Android | iOS |
|---|---|---|
| Ses encode | Opus | Opus (paketle gelen libopus) |
| Video encode | Cihaz encoder’ı varsa MediaCodec ile VP8/VP9 | Paketle gelen libvpx ile VP8/VP9 |
| Canlı/kayıt container’ı | WebM: Opus + VP8/VP9 | WebM: Opus + VP8/VP9 |
| SDK WebM decode | Media3/cihaz codec’i ile VP8, VP9, Opus | libvpx ile VP8/VP9; libopus ile Opus |
| Ogg ses decode | Media3/cihaz desteğine göre Opus, Vorbis, FLAC | SDK ile Opus; diğer desteklenenler AVFoundation ile |
| Yaygın native ses decode | Cihaz desteğine göre AAC, MP3, Opus, Vorbis, FLAC, AMR, PCM/WAV | AVFoundation ile AAC, HE-AAC, ALAC, MP3, PCM/WAV; SDK fallback ile Opus |
| Yaygın native video decode | Uyumlu cihaz decoder’ı varsa H.264/AVC, H.265/HEVC, VP8, VP9, AV1, MPEG-4, MPEG-2 | AVFoundation ile H.264/AVC, H.265/HEVC; SDK fallback ile VP8/VP9 |
| Container/protokol | MP4/M4V, Matroska/WebM, Ogg, MPEG-TS/PS, FLV, ADTS, AMR, FLAC, WAV, HLS, DASH | MP4/M4V/MOV, M4A, MP3, WAV, HLS; SDK ile WebM ve Ogg Opus |
Bu tablo uygulanan oynatma yollarını gösterir; her codec profilinin her cihazda çalışacağı garantisi değildir. Native destek işletim sistemi sürümüne, donanım decoder’ına, codec profile/level değerine, bit derinliğine, DRM'e, container yapısına ve sunucunun range/adaptive isteklerine bağlıdır. Container içermeyen ham Opus normal medya dosyası değildir; Ogg Opus veya WebM kullanılmalıdır.
Kurulum #
pubspec.yaml dosyanıza paketi ekleyin:
dependencies:
live_media_stream_sdk: ^0.4.7
Ardından genel API'yi içe aktarın:
import 'package:live_media_stream_sdk/live_media_stream_sdk.dart';
Yayın başlatılmadan önce kamera, mikrofon ve gerekiyorsa bildirim izinlerini istemek host uygulamanın sorumluluğundadır.
Android izinleri
Eklenti manifest dosyası kamera, mikrofon, foreground service ve bildirim izinlerini tanımlar. Android 13 ve sonrasında bildirim iznini uygulamanızdan istemelisiniz. Android'in kullanım-anı kısıtlamaları nedeniyle kamera veya mikrofon kullanan foreground service, uygulama görünür durumdayken başlatılmalıdır.
iOS izinleri
Host uygulamanın Info.plist dosyasına kullanım açıklamalarını ekleyin:
<key>NSCameraUsageDescription</key>
<string>Canlı video için kamera erişimi gereklidir.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Canlı ses için mikrofon erişimi gereklidir.</string>
Uygulama arka plandayken ses yayınını sürdürmek için ayrıca şunu ekleyin:
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
iOS arka planda kamera yakalamayı durdurur. Eklenti ses yayınını sürdürür; uygulama tekrar öne geldiğinde kamerayı yeniden başlatır ve ilk video karesini keyframe olarak üretir.
Eklenti hem Swift Package Manager hem CocoaPods destekler. Yeni Flutter projeleri ek işlem yapmadan SwiftPM kullanabilir; henüz geçiş yapmamış uygulamalar için CocoaPods desteği korunur.
Hızlı başlangıç #
Yukarıdaki Quick start örneği doğrudan kullanılabilir. Temel akış şöyledir:
LiveMediaStreamControlleroluşturun veinitialize()çağrısını bekleyin.- Üretilen veriler için
packets, olaylar içinevents, ölçümler içinstatisticsakışını dinleyin. - Bir
LiveStreamConfigurationilestart()çağrısını yapın. - Yayını bitirirken sırasıyla
stop()vedispose()çağrılarını mutlakaawaitedin.
Uzun iOS yayınları için VP8, 480p, 15–20 FPS ve 600–900 Kbit/s iyi bir başlangıç profilidir. Cihaz ciddi ölçüde ısındığında eklenti video yükünü otomatik azaltır. Termal durum kritik seviyeye ulaşırsa geçici olarak yalnızca ses yayınına geçer; cihaz soğuduğunda kamerayı yeniden başlatır.
Ayrıntılı kullanım #
Üretim ortamındaki tam sıra şöyledir:
- Seçilen medya modu için gereken izinleri alın.
- Ekran boyunca yaşayacak tek controller oluşturup initialize edin.
- Yayından önce paket, olay ve istatistik stream’lerine abone olun.
- Gerekiyorsa WebSocket transport ve kayıt ayarlarını oluşturun.
- Yayını başlatın; bütün kontrol işlemlerini
awaitedin. - Yayını durdurup tamamlanmış kayıt sonucunu alın.
- Kullanıcı isterse kaydı dosyalara aktarın veya paylaşın.
- Abonelikleri kapatıp controller’ı dispose edin.
1. Gerekli izinleri alın
start() çağrısından önce seçilen yayın modunun izinlerini kullanıcıdan alın:
audioOnly: mikrofonvideoOnly: kameraaudioVideo: kamera ve mikrofon- Android 13 ve sonrası: foreground service bildirimi kullanılacaksa bildirim
İzin isteme ekranları SDK tarafından açılmaz; bu işlem host uygulamaya aittir. Tercih ettiğiniz bir Flutter izin paketini veya mevcut native izin akışınızı kullanabilirsiniz.
2. Controller'ı oluşturun
Yayın ekranı boyunca aynı controller nesnesini saklayın:
late final LiveMediaStreamController controller;
Future<void> yayiniHazirla() async {
controller = LiveMediaStreamController();
await controller.initialize();
}
3. Paketleri, olayları ve istatistikleri dinleyin
İlk verileri kaçırmamak için yayın başlamadan önce stream'lere abone olun:
final paketAboneligi = controller.packets.listen((packet) {
// AudioPacket, VideoPacket veya WebmPacket gelir.
});
final olayAboneligi = controller.events.listen((event) {
// Kamera, ses, bağlantı, durum ve hata olayları gelir.
});
final istatistikAboneligi = controller.statistics.listen((statistics) {
debugPrint(
'upload=${statistics.uploadBitsPerSecond}, '
'cpu=${statistics.cpuUsage}, '
'termal=${statistics.thermalState}',
);
});
4. Yayını yapılandırıp başlatın
final ayarlar = LiveStreamConfiguration(
mediaMode: StreamMediaMode.audioVideo,
outputMode: StreamOutputMode.separateAudioVideo,
cameraPosition: CameraPosition.front,
resolution: StreamResolution.p480,
framesPerSecond: 20,
videoBitrate: 800000,
audioBitrate: 64000,
audioProcessing: const AudioProcessingConfiguration(
noiseSuppression: true,
automaticGainControl: true,
echoCancellation: true,
),
);
await controller.start(ayarlar);
Sunucunuz ses ve video paketlerini ayrı kabul ediyorsa separateAudioVideo,
oynatılabilir tek bir byte akışı bekliyorsa webm çıkışını seçin.
transport vermek zorunlu değildir. Transport verilmezse kodlanmış veriler
yine controller.packets üzerinden gelir; uygulama bu verileri kendisi
kaydedebilir veya kendi bağlantısıyla gönderebilir.
5. Aktif yayını kontrol edin
await controller.switchCamera(CameraPosition.back);
await controller.setMicrophoneEnabled(false);
await controller.setCameraEnabled(false);
await controller.setMicrophoneEnabled(true);
await controller.setCameraEnabled(true);
await controller.setVideoBitrate(600000);
await controller.requestVideoKeyFrame();
await controller.pause();
await controller.resume();
Video komutlarını yalnızca video açıkken çağırın. Bir durum değiştirme işlemi
bitmeden diğerini başlatmamak için tüm çağrıları await edin.
6. Yayını güvenli biçimde kapatın
await controller.stop();
await paketAboneligi.cancel();
await olayAboneligi.cancel();
await istatistikAboneligi.cancel();
await controller.dispose();
Socket bağlantısını kapatmadan veya yayın ekranından çıkmadan önce stop()
çağrısını mutlaka await edin. Böylece bekleyen paketler ve WebM akışının son
baytları kaybolmaz.
Kayıt açıksa stop() tamamlanan dosyanın bütün bilgilerini döndürür:
final durdurmaSonucu = await controller.stop();
final kayit = durdurmaSonucu.recording;
if (kayit != null && kayit.isCompleted) {
debugPrint(
'${kayit.filePath} • ${kayit.fileSizeKilobytes} KB '
'• ${kayit.duration}',
);
// Native Dosyalar/Kaydet ekranı; kullanıcı vazgeçerse null döner.
final hedefUri = await controller.exportRecording(kayit);
// Android/iOS paylaşım ekranı: WhatsApp, AirDrop vb.
await controller.shareRecording(kayit);
}
Dosya, mux işleminin güvenli tamamlanabilmesi için önce uygulamaya özel alanda
oluşturulur. exportRecording ile Documents, Downloads, iCloud Drive veya
başka bir sağlayıcı seçilir. Dosya adı opsiyoneldir; .webm otomatik eklenir.
SDK klasöründe kalan kayıtları listeleyip yönetebilirsiniz:
final kayitlar = await controller.listRecordings();
await controller.exportStoredRecording(kayitlar.first);
await controller.shareStoredRecording(kayitlar.first);
await controller.deleteRecording(kayitlar.first);
Silme işlemi yalnızca SDK'nın yönettiği klasörde doğrudan bulunan .webm
kayıtlarıyla sınırlıdır; başka dosya yolları silinemez.
SDK sisteminizde nasıl konumlanır? #
Kamera + mikrofon
│
▼
Native yakalama → Opus + VP8/VP9 kodlama
│
├── controller.packets ──→ transport / WebSocket relay
│
├── native WebM kayıt ──→ upload edilebilir .webm
│
└── Flutter texture ──→ yerel kamera önizlemesi
Medya dosyası / URL / WebSocket WebM akışı
│
▼
Native player → video texture + ses çıkışı → Flutter kontrolleri
Encode ve decode işlemleri cihazın native katmanında yapılır. WebSocket sunucusu yalnızca binary frame’leri aktaracaksa WebM’i açmak, decode etmek, transcode etmek veya ses/video karıştırmak zorunda değildir. Kimlik doğrulama, oda yetkisi, yavaş istemci kontrolü, saklama, izleme ve yatay ölçekleme ise uygulamanın backend sorumluluklarıdır.
Tüm yayın ayarları #
LiveStreamConfiguration
| Alan | Varsayılan | Açıklama |
|---|---|---|
transport |
null |
Gönderim katmanı. Null olduğunda paketler yalnız controller.packets üzerinden alınır; yerel kayıt için idealdir. |
socketMessageBuilder |
SDK builder | Paketleri uygulamanın socket metadata ve mesaj biçimine dönüştürür. |
mediaMode |
audioVideo |
audioOnly, videoOnly veya audioVideo; istenecek izinleri de belirler. |
videoCodec |
vp8 |
VP8 veya VP9. Uyumluluk ve ısınma açısından VP8 güvenli başlangıçtır. |
audioCodec |
opus |
Native Opus ses kodlama. |
resolution |
p720 |
480p, 720p veya 1080p. Çözünürlük arttıkça ısı, pil ve bitrate ihtiyacı artar. |
outputMode |
separateAudioVideo |
Bağımsız encode paketleri veya sıralı canlı WebM parçaları. |
cameraPosition |
back |
Başlangıçta ön veya arka kamera. |
qualityMode |
automatic |
Kalite stratejisi; otomatik mod iOS termal uyarlamasına izin verir. |
framesPerSecond |
30 |
İstenen FPS. Uzun yayınlarda 15–20 daha serindir. |
videoBitrate |
1500000 |
Video bit/s; yayın sırasında değiştirilebilir. |
audioBitrate |
64000 |
Opus bit/s; konuşma için 32–64 Kbit/s iyi başlangıçtır. |
audioProcessing |
varsayılanlar | Mikrofon, gain, kanal/sample rate, AGC, AEC, NS ve VAD. |
recording |
null |
Verildiğinde native WebM dosya kaydını açar. |
enableForegroundService |
true |
Android’de zorunlu foreground bildirim yaşam döngüsünü yönetir. |
| bildirim başlığı/açıklaması | Türkçe varsayılan | Android foreground service bildirim metni. |
overlays |
boş | Desteklenen medya overlay yapılandırması. |
AudioProcessingConfiguration
| Alan | Varsayılan | Açıklama |
|---|---|---|
automaticGainControl |
true |
Cihaz destekliyorsa native otomatik kazanç ister. |
echoCancellation |
true |
AEC/voice-chat yönlendirmesi ister; cihaz desteği değişebilir. |
noiseSuppression |
true |
Native gürültü azaltma ister. |
voiceActivityDetection |
false |
Sessizliği algılar; Android sessiz frame’leri encode öncesi atabilir. |
inputDevice |
varsayılan | getAudioInputDevices() listesinden mikrofon seçer. |
gain |
1.0 |
Android yazılımsal gain: 0.0–8.0; 1 üzerindeyken clipping izlenmelidir. |
channelMode |
mono |
Rota destekliyorsa mono veya stereo. |
sampleRate |
48000 |
8, 12, 16, 24 veya 48 kHz. |
silenceThresholdDb |
-50 |
Bunun altındaki giriş VAD tarafından sessiz kabul edilir. |
vadHangoverMilliseconds |
250 |
Seviye düşünce konuşmayı kısa süre daha aktif tutar. |
Ses efektleri garanti değil, istektir. Cihazın gerçekten açtığı efektleri canlı
istatistiklerdeki noiseSuppressorEnabled,
automaticGainControlEnabled ve echoCancelerEnabled alanlarından okuyun.
Controller işlemleri #
| API | Ne zaman kullanılır? |
|---|---|
initialize() |
Cihazları okumadan ve ilk yayından önce bir kez. |
start(configuration) |
Capture, encoder, transport, kayıt ve foreground yaşam döngüsünü başlatır. |
pause() / resume() |
Aktif yayını geçici durdurur/devam ettirir. |
stop() |
Encoder ve muxer’ı flush eder, LiveStreamStopResult döndürür. Mutlaka await edin. |
dispose() |
Ekran/servis tamamen kapanırken kanal kaynaklarını bırakır. |
getAudioInputDevices() |
Dahili, kablolu, Bluetooth ve USB mikrofonları listeler. |
switchCamera() |
Video yayınında ön/arka kamerayı değiştirir. |
setMicrophoneEnabled(enabled) |
Yayını durdurmadan mikrofon paketlerini açar veya kapatır. |
setCameraEnabled(enabled) |
Yayını durdurmadan kamera paketlerini açar veya kapatır; yeniden açarken keyframe ister. |
setVideoBitrate() |
Yayını kesmeden bant genişliği ve ısıyı ayarlar. |
requestVideoKeyFrame() |
Yeni veya yeniden bağlanan izleyiciyi senkronlar. |
getVideoTextureId() |
Desteklenen platformda yerel kamera preview texture’ını verir. |
listRecordings() |
SDK klasöründeki tamamlanmış WebM kayıtlarını listeler. |
deleteRecording(recording) |
SDK klasöründeki bir kaydı siler. |
exportStoredRecording(recording) |
Listelenen kayıt için sistem kaydetme ekranını açar. |
shareStoredRecording(recording) |
Listelenen kayıt için paylaşım ekranını açar. |
İlk verileri kaçırmamak için packets, events ve statistics
aboneliklerini start() öncesinde açın. Ekranı kapatırken önce stop(), sonra
abonelik iptalleri ve en son dispose() çağırın.
Hangi çıkış biçimini seçmeliyim? #
- Protokol ses/video paketlerini ayrı yönetecekse
separateAudioVideo. - İzleyici doğrudan demux edilebilir sıralı byte akışı istiyorsa
webm. - Ağ olmasa da kayıt istiyorsanız
transport: nullverecording. - SignalR, broker veya mevcut native socket’iniz varsa
controller.packetsüzerinden kendiLiveMediaTransportkatmanınızı kullanın. - Normal binary WebSocket relay yeterliyse
WebSocketLiveMediaTransportkullanın.
WebSocket payload’ını base64’e çevirmeyin. WebM parça sırasını koruyun, backpressure uygulayın ve yeni/yeniden bağlanan izleyici için keyframe isteyin.
Hata yönetimi ve üretim kuralları #
try {
await controller.start(ayarlar);
} on PlatformException catch (hata) {
log('${hata.code}: ${hata.message}');
}
final olaylar = controller.events.listen((olay) {
if (olay is LiveStreamError) {
log('${olay.code}: ${olay.message}');
}
});
- Aynı controller ile eşzamanlı iki yayın başlatmayın.
- Başlatma/yayın sırasında yapılandırma kontrollerini kilitleyin.
- Lifecycle çağrılarını eşzamanlı yapmayın; hepsini await edin.
- Kullanıcı başka klasöre erişim vermediyse app-scoped dosya yolu kullanın.
wss://, kimlik doğrulama, oda yetkisi, payload/rate sınırı kullanın.- Düşen kare, CPU, bellek, termal durum, upload bitrate ve reconnect değerlerini üretimde izleyin.
- WebSocket relay; transcoder, DVR, CDN, SFU veya yetkilendirme servisi değildir. Ürününüz ihtiyaç duyuyorsa bunları ayrıca kurun.
Gelişmiş kullanım örnekleri #
Harici mikrofonu otomatik seçerek kaliteli ses yayını
Bağlı mikrofonları listeleyip USB, kablolu kulaklık, Bluetooth ve dahili mikrofon sırasıyla en uygun girişi seçebilirsiniz:
final mikrofonlar = await controller.getAudioInputDevices();
final mikrofon = AudioInputDeviceSelector.select(mikrofonlar);
await controller.start(
LiveStreamConfiguration(
mediaMode: StreamMediaMode.audioOnly,
audioCodec: AudioCodec.opus,
audioBitrate: 64000,
audioProcessing: AudioProcessingConfiguration(
inputDevice: mikrofon,
channelMode: AudioChannelMode.mono,
sampleRate: 48000,
gain: 1.25,
noiseSuppression: true,
automaticGainControl: true,
echoCancellation: true,
voiceActivityDetection: true,
silenceThresholdDb: -50,
vadHangoverMilliseconds: 250,
),
),
);
Android'de VAD açıkken eşik altında kalan sessiz frame'ler kodlanmayarak bant
genişliği ve işlemci kullanımı azaltılabilir. Native ses efektlerinin gerçekten
etkin olup olmadığını LiveStreamStatistics içindeki
noiseSuppressorEnabled, automaticGainControlEnabled ve
echoCancelerEnabled alanlarından kontrol edin; destek cihazdan cihaza değişir.
Isınmayı gözeten kamera profili
Uzun süreli yayınlar için 480p ve 20 FPS ile başlayıp ağ veya cihaz durumuna göre bitrate'i yayın sırasında değiştirebilirsiniz:
await controller.start(
const LiveStreamConfiguration(
mediaMode: StreamMediaMode.audioVideo,
videoCodec: VideoCodec.vp8,
resolution: StreamResolution.p480,
cameraPosition: CameraPosition.back,
framesPerSecond: 20,
videoBitrate: 800000,
audioBitrate: 64000,
qualityMode: QualityMode.automatic,
),
);
// Ağ zayıfladığında yayını kesmeden düşürün.
await controller.setVideoBitrate(550000);
// Kamerayı değiştirip izleyicilere yeni bir tam kare gönderin.
await controller.switchCamera(CameraPosition.front);
await controller.requestVideoKeyFrame();
iOS'ta termal koruma ayrıca otomatik çalışır. Güncel durumu
statistics.thermalState ve statistics.thermalLimited alanlarından
izleyebilirsiniz.
Native WebM kayıt
Kayıt sırasında ikinci bir kamera, mikrofon veya encoder açılmaz. Canlı yayında üretilen Opus ve VP8/VP9 verileri aynı anda WebM dosyasına yazılır. Yalnız ses, yalnız video ve sesli video modlarının tamamında kullanılabilir:
await controller.start(
const LiveStreamConfiguration(
mediaMode: StreamMediaMode.audioVideo,
recording: RecordingConfiguration(
// İki değer de isteğe bağlıdır.
directoryPath: '/uygulamanin/yazabildigi/klasor',
fileName: 'egitim-yayini.webm',
),
),
);
final durdurmaSonucu = await controller.stop();
final kayit = durdurmaSonucu.recording;
if (kayit?.isSuccessful == true) {
print(kayit!.filePath); // Upload veya paylaşım için kullanın.
print(kayit.fileSizeBytes);
print(kayit.fileSizeKilobytes);
print(kayit.duration);
print('${kayit.width}x${kayit.height}');
}
Kayıt sürerken LiveStreamStatistics.isRecording ve recordingDuration
alanları canlı geçen süreyi verir. stop() tamamlandığında kesin medya süresi
MediaRecordingResult.duration alanındadır.
Kayıt önce uygulamanın güvenli alanında finalize edilir. Ardından kullanıcıya görünür bir konum seçtirilebilir veya native paylaşım ekranı açılabilir:
final kayit = (await controller.stop()).recording;
if (kayit?.isSuccessful == true) {
// Android'de sistem Kaydet/SAF, iOS'ta Files seçicisi açılır.
final hedef = await controller.exportRecording(kayit!);
// WhatsApp, Telegram, AirDrop, Drive, Mail vb.
await controller.shareRecording(kayit);
}
Kullanıcı yalnız dosya adını yazar; .webm otomatik eklenir. Uygulama
tarafından yönetilen ileri seviye senaryolarda doğrudan klasör yolu hâlâ
verilebilir; son kullanıcı arayüzünde Android scoped storage ve iOS sandbox
kurallarına uygun sistem seçicisi tercih edilmelidir.
Klasör verilmezse Android
Android/data/<paket_adı>/files, iOS ise uygulamanın Documents klasörünü
kullanır. Dosya adı verilmezse benzersiz bir .webm adı üretilir. Aynı isimli
dosya, overwriteExisting: true açıkça seçilmedikçe ezilmez. Dönen
MediaRecordingResult; tam dosya yolu, bayt/KB/MB boyutu, süre, codec ve
bitrate değerleri, örnekleme hızı, kanal sayısı, çözünürlük, FPS, paket/kare/
düşen kare/keyframe sayıları, dönüş bilgisi, başlangıç-bitiş zamanı ve hata
bilgisini içerir.
controller.stop() bir LiveStreamStopResult döndürür. Kayıt istenmediyse
recording null olur. Kayıt varsa şu alanlar upload öncesi ihtiyaç duyulan
bilgilerin tamamını verir:
| Alan | Anlamı |
|---|---|
status, isSuccessful |
Kayıt başarıyla finalize edildi mi? |
filePath, directoryPath, fileName |
Upload/paylaşım için çözülmüş dosya adresi. |
| byte, KB ve MB boyutları | Kesin ve yardımcı dosya boyutları. |
duration, startedAt, endedAt |
Medya süresi ve UTC başlangıç/bitiş zamanları. |
mediaMode, hasAudio, hasVideo |
Dosyanın medya içeriği. |
| codec ve bitrate alanları | Encode biçimi ve yapılandırılan hızlar. |
sampleRate, channels |
Ses formatı. |
| çözünürlük, FPS, dönüş, aspect ratio | Video/görüntüleme formatı. |
| paket, kare, düşen kare, keyframe sayaçları | Kayıt kalite tanılaması. |
errorCode, errorMessage |
Native kayıt hatası. |
stop() tamamlanmadan dosyayı upload etmeyin; muxer henüz finalize edilmemiş
olabilir.
Kayıt ve socket gönderimi birbirinden bağımsız hatlardır. Yavaş socket native
dosya yazımını bekletemez: sınırlı gönderim kuyruğu ses ve keyframe'e öncelik
verir, atılabilir video karelerinden sonra yeni keyframe ister. Sıralı WebM
socket akışı güvenle küçültülemiyorsa transport_backpressure olayı üretilerek
socket gönderimi durur, fakat yerel kayıt devam eder.
Audio ve video player
Tek player controller; platformun desteklediği cihaz dosyalarını ve HTTP(S) adreslerini oynatır. Sistem player’ının açamadığı WebM için SDK’nın özel libvpx/Opus hattı kullanılır. Byte stream ve WebSocket canlı yayınları SDK’nın sıralı WebM canlı protokolünü kullanır:
final oynatici = LiveMediaPlayerController();
await oynatici.open(
const MediaFileSource('/dosya/yolu/video.mp4'),
autoplay: true,
);
// URL için:
await oynatici.open(
MediaUrlSource.parse('ornek.com/master.m3u8'),
);
// Ham WebM parçaları veya SDK binary frame'leri gönderen WebSocket için:
await oynatici.open(
WebSocketMediaSource(Uri.parse('wss://ornek.com/izle/oda-42')),
autoplay: true,
);
MediaUrlSource.parse, kullanıcının yazdığı adreste protokol yoksa otomatik
olarak https:// ekler; açıkça yazılan http:// veya https:// değerini
korur. MediaUrlSource(Uri(...)) kullanıldığında da controller, kaynak
açılmadan önce eksik protokolü HTTPS olarak tamamlar.
Video arayüzü:
LiveMediaVideoPlayer(
controller: oynatici,
// Boş bırakılırsa videonun 4:3, 16:9, 9:16 vb. oranı kullanılır.
scale: 0.9,
fit: BoxFit.contain,
fullscreenConfiguration: const FullscreenConfiguration(
orientation: FullscreenOrientation.landscape,
),
)
scale yerine width ve/veya height verilebilir. Video player; oynat/duraklat,
progress bar üzerinden ileri-geri sarma, izlenen/toplam süre, ses, sessize alma,
0.25x–3x hız, tam ekran ve yatay/dikey görüntüleme kontrollerini içerir. Yalnız
ses için LiveMediaAudioPlayer(controller: oynatici) kullanılır. Canlı
WebSocket yayını ileri-geri sarılamaz; kayıtlı ve seek edilebilir kaynaklarda
progress ve sarma açıktır.
Tam özelleştirilebilir kontroller
Video yüzeyi, medya state'i ve kontrol arayüzü birbirinden ayrıdır. Üç kullanım seviyesi vardır:
- Hazır kontrol arayüzü için
LiveMediaVideoPlayerdoğrudan kullanılır. controlsBuilderverilirse SDK kontrol katmanı, uygulamanın kendi Flutter widget'larıyla değiştirilir. Bu kontroller görünür kalır ve gesture'larını kendisi yönetir; SDK otomatik gizleme yapmaz veya yüzey dokunuşlarını yutmaz.- Tamamen harici yerleşim için
LiveMediaVideoSurfacedoğrudan kullanılır. Hiç overlay istenmiyorsaLiveMediaVideoPlayeriçindeshowControls: falsekullanılabilir.
LiveMediaPlayerController, ValueListenable<LiveMediaPlayerState>'dir ve
ayrıca broadcast states akışını sunar. Özel progress bar örneği:
LiveMediaVideoPlayer(
controller: oynatici,
controlsBuilder: (context, controller, state) {
final zaman = state.timeline;
return Align(
alignment: Alignment.bottomCenter,
child: Slider(
value: (zaman.progress ?? 0) * 100,
max: 100,
onChanged: state.capabilities.canSeek && zaman.hasDuration
? (deger) => controller.seekTo(
zaman.duration * (deger / 100),
)
: null,
),
);
},
)
state.timeline (veya oynatici.timeline); konum, buffer konumu, süre,
canlı/seek bilgisi, platform sağlarsa seek edilebilir sınırlar ve süre sonluysa
progress değerini verir. Canlı ya da sınırı olmayan timeline'da progress
null olur; bu kaynaklara yapay sabit süre verilmemelidir.
state.capabilities (veya oynatici.capabilities), hangi butonların o an
anlamlı olduğunu bildirir: canPlay, canPause, canSeek, canSetVolume,
canMute, canChangePlaybackSpeed, canChangeAudioOutput ve canGoLive.
Controller'da play, pause, stop, seek/seekTo, seekBy,
togglePlayback, setVolume, setMuted, setPlaybackSpeed/
setPlaybackRate, setAudioOutput ve goLive bulunur.
Seek edilebilir ses veya video tamamlandıktan sonra play(), oynatmaya
başlamadan önce otomatik olarak başa sarar. Bu davranış hem SDK kontrollerinde
hem de uygulamanın kendi play düğmesinde aynıdır; canlı kaynaklar mevcut
davranışını korur.
Harici düzen için LiveMediaVideoSurface(controller: oynatici) çizilip
ValueListenableBuilder<LiveMediaPlayerState> veya oynatici.states
dinlenebilir. Böylece markalı progress bar, bölüm işaretleri, büyük erişilebilir
butonlar, canlıya dön düğmesi, izleme analitiği veya tamamen farklı bir player
deneyimi oluşturulabilir.
Kaynak açıldıktan sonra ses çıkışı seçilebilir:
await oynatici.setAudioOutput(PlayerAudioOutput.speaker); // Hoparlör
await oynatici.setAudioOutput(PlayerAudioOutput.receiver); // Ahize
await oynatici.setAudioOutput(PlayerAudioOutput.systemDefault); // Sistem
Ahize seçimi yalnız dahili telefon ahizesi bulunan cihazlarda kullanılabilir.
systemDefault seçildiğinde kablolu, Bluetooth, AirPlay ve diğer harici
çıkışların yönetimi işletim sistemine bırakılır.
Ekran kapatılırken oynatici.dispose() çağrılmalıdır. Mevcut native altyapıda
her Flutter engine için aynı anda bir aktif player oturumu desteklenir.
Format seçimi ve native fallback
Player, dosya WebM olmadığı için kaynağı reddetmez:
| Kaynak | Android | iOS |
|---|---|---|
| Yerel dosya / HTTP(S) URL | Media3 extractor + cihaz decoder’ı | AVPlayer/AVFoundation |
| HLS | Media3 HLS | AVPlayer |
| MPEG-DASH | Media3 DASH | Cihaz/AVFoundation desteğine bağlı; Apple için HLS önerilir |
| WebM VP8/VP9 + Opus dosya/URL | Media3 | SDK WebM demuxer + libvpx + Opus |
Ogg Opus .opus / .ogg / .oga dosya/URL |
Media3 | SDK Ogg demuxer + Opus |
| WebSocket / byte stream | SDK WebM canlı protokolü | SDK WebM canlı protokolü |
iOS'ta SDK, Opus kanal eşlemesini OpusHead içinden okur. 5.1 dahil çok kanallı
WebM/Ogg Opus kaynakları libopus multistream decoder ile çözülür ve oynatma
için stereo çıkışa indirgenir.
Native tarafta yaygın olarak MP4/M4V/MOV içinde H.264/HEVC + AAC, M4A/AAC, MP3 ve WAV/PCM oynatılır. Gerçek destek; işletim sistemi, cihaz decoder’ı, codec profile/level, bit derinliği, DRM ve container-track birleşimine bağlıdır. Android Media3 ayrıca iç codec mevcutsa Matroska/WebM, Ogg, FLV, MPEG-TS, HLS ve DASH gibi formatları tanır.
.opus dosyasının standart Ogg container içinde Opus paketleri taşıması
beklenir. Herhangi bir container ve başlık içermeyen ham Opus paketleri,
kendi başına tanımlanabilir ve seek edilebilir bir medya dosyası değildir;
önce Ogg veya WebM içine mux edilmelidir.
Dosya uzantısı yalnız ipucudur. Native backend medya track’lerini inceler ve bulunan gerçek container/codec bilgisini döndürür. Container geçerli olsa bile codec profili desteklenmiyorsa, DRM lisansı yoksa, sunucu gerekli range/adaptive isteklerini karşılamıyorsa veya dosya eksikse oynatma başarısız olabilir.
WebSocket daha dar kapsamlıdır: normal MP4/MOV dosyasının rastgele parçaları kendiliğinden canlı akış olmaz. Mevcut SDK canlı protokolü sıralı WebM parçaları taşır. WebSocket üzerinden fMP4 veya MPEG-TS için açık stream türü ve ona uygun incremental parser gerekir.
Player kaynakları ve işlemleri:
| API | Kullanımı |
|---|---|
MediaFileSource(path) |
Uygulamanın okuyabildiği medya dosyası; native backend otomatik seçilir. |
MediaUrlSource(uri, headers:) |
HTTP(S) medya/adaptive manifest; eksik protokol otomatik HTTPS yapılır. |
MediaUrlSource.parse(metin, headers:) |
Metin girişi için önerilir; ornek.com/ses.mp3 adresini https:// yazmadan kabul eder. |
WebSocketMediaSource(uri, protocols:) |
Ham WebM parçaları veya SDK-framed binary mesajlar. |
MediaByteStreamSource(stream) |
Uygulamanın ürettiği sıralı WebM byte stream. |
open(source, autoplay:) |
Önceki kaynağı kapatır, native decoder’ı hazırlar ve MediaInfo döndürür. |
play, pause, stop |
Oynatma yaşam döngüsü. |
seek |
Yalnız kayıtlı/seek edilebilir kaynak. |
seekTo, seekBy |
Özel kontroller için mutlak ve göreli sarma yardımcıları. |
togglePlayback() |
Mevcut duruma göre oynatır veya duraklatır. |
| volume/mute | 0–1 ses ve sessiz kontrolü. |
setPlaybackSpeed, setPlaybackRate |
0.25–3.0 oynatma hızı. |
goLive() |
Canlı kaynağın en güncel tampon konumuna döner. |
timeline, capabilities, states |
Özel kontroller için timeline, kullanılabilir aksiyonlar ve state akışı. |
LiveMediaVideoSurface |
Yalnız video texture'ı; tamamen harici player düzeni için. |
LiveMediaVideoPlayer.controlsBuilder |
Varsayılan video içi kontrol katmanını değiştirir. |
oynatici.state; opening, ready, playing, paused, buffering, completed, failed
ve disposed durumlarıyla birlikte konum, buffer, süre, ses, hız, texture,
metadata ve hata bilgisini verir.
Video boyutlandırma kuralları:
- Hiç ölçü verilmezse videonun bulunan 4:3, 16:9, 9:16 vb. oranı kullanılır.
- Yalnız
widthverilirse yükseklik, yalnızheightverilirse genişlik orandan hesaplanır. - İkisi birlikte verilirse player o tasarım kutusuna yerleşir.
scale, parent genişliğine göre oransal büyüklük verir.scaleile width/height aynı anda kullanılmaz.BoxFit.containtüm kareyi korur;coveralanı doldurup kenarları kırpabilir.- Tam ekran yönü automatic, portrait, landscape veya all seçilebilir.
WebSocket ve özel taşıma katmanı #
SDK herhangi bir sunucu protokolüne bağımlı değildir.
WebSocketLiveMediaTransport kullanabilir veya SignalR, native WebSocket ya
da kendi protokolünüz için LiveMediaTransport arayüzünü uygulayabilirsiniz.
Yukarıdaki WebSocket transport örneğinde bağlantı ve
mesaj metadata'sı gösterilmiştir.
Pakete dahil WebSocket taşıyıcısı, JSON metadata ile ham binary medya verisini tek bir binary frame içinde gönderir. Base64 kullanılmadığı için medya verisi gereksiz yere büyümez.
Binary frame biçimi
WebSocketLiveMediaTransport, her LiveSocketMessage nesnesini aşağıdaki
wire formatıyla kodlar:
| Başlangıç | Boyut | İçerik |
|---|---|---|
0 |
1 byte | Protokol sürümü. Güncel değer 1'dir. |
1 |
4 byte | Unsigned 32-bit big-endian integer olarak JSON metadata uzunluğu. |
5 |
Metadata uzunluğu | UTF-8 olarak kodlanmış JSON metadata. |
5 + metadata uzunluğu |
Kalan byte'lar | Ham binary medya payload'ı. Payload yoksa boştur. |
Başka bir ifadeyle:
+-----------+----------------------+---------------------+--------------------+
| sürüm | metadata uzunluğu | JSON metadata | ham medya payload |
| 1 byte | 4 byte, big-endian | UTF-8, değişken | kalan byte'lar |
+-----------+----------------------+---------------------+--------------------+
JSON metadata, LiveSocketMessage alanlarını temsil eder:
| Alan | Tür | Açıklama |
|---|---|---|
action |
string | media gibi zorunlu mesaj aksiyonu. |
command |
string veya null | publish gibi isteğe bağlı uygulama komutu. |
groupName |
string veya null | İsteğe bağlı oda veya grup kimliği. |
receiverConnectionId |
string veya null | İsteğe bağlı hedef bağlantı kimliği. |
senderConnectionId |
string veya null | İsteğe bağlı gönderen bağlantı kimliği. |
userId |
integer veya null | İsteğe bağlı uygulama kullanıcı kimliği. |
userType |
string veya null | Uygulamaya özel isteğe bağlı kullanıcı türü. |
headers |
JSON object veya null | Özel header'lar ve medya metadata'sı. |
payloadLength |
integer | JSON metadata'dan sonra gelen ham payload'ın byte sayısı. |
LiveSocketMessage.fromMediaPacket() ile oluşturulan mesajların headers
alanına mediaType, sequenceNumber, timestampUs ve payloadLength
eklenir. Ses paketleri ayrıca codec, sampleRate, channels ve bitrate;
video paketleri ise codec, width, height ve isKeyFrame içerir.
mediaType; audio, video veya webm değerini alır.
Yalnızca JSON içeren kontrol mesajları da kabul edilir. Text frame veya ilk
byte'ı { (0x7B) olan UTF-8 binary frame, binary payload olmadan doğrudan
LiveSocketMessage metadata'sı olarak çözülür. Yalnız relay görevi yapan
sunucular SDK binary frame'lerini değiştirmeden iletebilir. Mesajları inceleyen
veya yönlendiren sunucular içeriği kullanmadan önce protokol sürümünü, metadata
uzunluğunu, belirtilen payload uzunluğunu ve uygulamaya özel yetkilendirmeyi
doğrulamalıdır.
Basit WebSocket sunucusu nasıl olur?
Sunucu yalnızca yayın verisini diğer bağlantılara aktaracaksa codec çözmesine veya mux işlemi yapmasına gerek yoktur. Yukarıdaki Minimal WebSocket relay örneği, gelen binary frame'leri diğer istemcilere olduğu gibi ileten çok küçük bir Node.js sunucusu gösterir.
Bu örnek yalnızca mimariyi anlatır. Gerçek kullanımda wss://, kimlik
doğrulama, odalar, yetkilendirme, hız sınırı, yavaş istemci kontrolü, izleme ve
yatay ölçekleme eklenmelidir. Alıcı Flutter uygulaması
WebSocketLiveMediaTransport.decode() ile metadata ve ham payload'ı ayırabilir.
WebM çıkışı #
outputMode: StreamOutputMode.webm seçildiğinde controller.packets, sıralı
WebmPacket parçaları üretir. Bu parçaları sıralarını değiştirmeden
birleştirin. Muxer'ın son baytları bağlantı kapanmadan gönderilebilsin diye
yayını durdururken her zaman await controller.stop() kullanın.
Kamera önizlemesi #
Controller initialize edildikten sonra controller.getVideoTextureId() ile
texture kimliğini alın ve LiveMediaPreview(textureId: textureId) bileşenine
verin. Aynı genel API Android ve iOS'ta çalışır; video yayını başladığında
kareler görünür, yayın durduğunda texture üretimi durur.
Ses işleme ve istatistikler #
AudioProcessingConfiguration üzerinden gain, giriş cihazı, kanal modu,
örnekleme hızı, yankı giderme, gürültü azaltma, otomatik kazanç, ses etkinliği
algılama ve sessizlik eşiği ayarlanabilir. Platform ayrıntıları için
AUDIO_FEATURES.md dosyasına bakın.
controller.events ve controller.statistics; ses seviyesi, yayın durumu,
kodlama/yükleme bitrate değerleri, CPU kullanımı, etkin ses efektleri ve iOS
termal durumu gibi bilgileri sağlar.
Örnek uygulama #
example uygulaması etkileşimli bir öğrenme laboratuvarıdır. Sunucu olmadan
yerel capture, bütün yayın/ses/video ayarları, native kayıt yolu ve adı, zengin
kayıt sonucu, canlı istatistikler ve son kayıt/dosya/URL/WebSocket player
senaryolarını tek ekranda öğretir.
cd example
flutter pub get
flutter run
Önce arayüzde WebSocket’i kapalı tutarak capture ve kaydı doğrulayın. Sonra
Örnek arayüzden WebSocket gönderimini açın, IP adresini veya WebSocket URL’sini
girin ve relay sunucunuzu test edin. Örnek, yerel IP adresini otomatik ws://,
alan adını wss:// yapar; kayıt ile socket gönderiminin aynı anda çalışmasını
da gösterir.
Lisans #
Paket MIT Lisansı ile sunulur. iOS paketine dahil edilen libvpx ve libopus
ikililerinin ayrı koşulları
THIRD_PARTY_NOTICES.md dosyasında belirtilmiştir.