Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for WASM builds #593

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/src/core/room.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import '../utils.dart';
import 'engine.dart';

import '../track/web/_audio_api.dart'
if (dart.library.html) '../track/web/_audio_html.dart' as audio;
if (dart.library.js_interop) '../track/web/_audio_html.dart' as audio;

/// Room is the primary construct for LiveKit conferences. It contains a
/// group of [Participant]s, each publishing and subscribing to [Track]s.
Expand Down
2 changes: 1 addition & 1 deletion lib/src/support/platform.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

import 'dart:io';

import 'platform/io.dart' if (dart.library.html) 'platform/web.dart';
import 'platform/io.dart' if (dart.library.js_interop) 'platform/web.dart';

// Returns the current platform which works for both web and devices.
PlatformType lkPlatform() => lkPlatformImplementation();
Expand Down
13 changes: 9 additions & 4 deletions lib/src/support/platform/web.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import 'dart:js' as js;
import 'dart:js_interop';
import 'dart:js_interop_unsafe';

import 'package:flutter/foundation.dart';

import 'package:platform_detect/platform_detect.dart';
import 'package:web/web.dart';

import '../platform.dart';

Expand All @@ -32,12 +34,15 @@ bool lkE2EESupportedImplementation() {
}

bool isScriptTransformSupported() {
return js.context['RTCRtpScriptTransform'] != null;
return window.hasProperty('RTCRtpScriptTransform'.toJS).isDefinedAndNotNull;
}

bool isInsertableStreamSupported() {
return js.context['RTCRtpSender'] != null &&
js.context['RTCRtpSender']['prototype']['createEncodedStreams'] != null;
return window.hasProperty('RTCRtpSender'.toJS).isDefinedAndNotNull &&
((window.getProperty('RTCRtpSender'.toJS) as JSObject)
.getProperty('prototype'.toJS) as JSObject)
.getProperty('createEncodedStreams'.toJS)
.isDefinedAndNotNull;
}

BrowserType lkBrowserImplementation() {
Expand Down
2 changes: 1 addition & 1 deletion lib/src/support/websocket.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// limitations under the License.

import '../support/disposable.dart';
import 'websocket/io.dart' if (dart.library.html) 'websocket/web.dart';
import 'websocket/io.dart' if (dart.library.js_interop) 'websocket/web.dart';

class WebSocketException implements Exception {
final String message;
Expand Down
9 changes: 6 additions & 3 deletions lib/src/support/websocket/web.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ class LiveKitWebSocketWeb extends LiveKitWebSocket {
logger.warning('$objectId already disposed, ignoring received data.');
return;
}
dynamic data =
_.data is ByteBuffer ? (_.data as ByteBuffer).asUint8List() : _.data;
dynamic data = _.data.instanceOfString('ArrayBuffer')
? (_.data as JSArrayBuffer).toDart.asUint8List()
: _.data;
options?.onData?.call(data);
});
_closeSubscription = _ws.onClose.listen((_) async {
Expand All @@ -64,7 +65,9 @@ class LiveKitWebSocketWeb extends LiveKitWebSocket {
}

@override
void send(List<int> data) => _ws.send(Uint8List.fromList(data).toJS);
void send(List<int> data) {
_ws.send(Uint8List.fromList(data).toJS);
}

static Future<LiveKitWebSocketWeb> connect(
Uri uri, [
Expand Down
4 changes: 2 additions & 2 deletions lib/src/track/remote/audio.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ import '../audio_management.dart';
import '../local/local.dart';
import 'remote.dart';

import '../web/_audio_api.dart' if (dart.library.html) '../web/_audio_html.dart'
as audio;
import '../web/_audio_api.dart'
if (dart.library.js_interop) '../web/_audio_html.dart' as audio;

class RemoteAudioTrack extends RemoteTrack
with AudioTrack, RemoteAudioManagementMixin {
Expand Down
29 changes: 0 additions & 29 deletions lib/src/track/web/_audio_context.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import 'dart:js_util' as jsutil;

import 'package:js/js.dart';

@JS()
external dynamic get undefined;

enum AudioContextState {
suspended('suspended'),
running('running'),
Expand All @@ -31,25 +24,3 @@ enum AudioContextState {
values.map(fromValue);
const AudioContextState(this.value);
}

@anonymous
@JS()
@staticInterop
class AudioContextOptions {
external factory AudioContextOptions(
{dynamic latencyHint, double? sampleRate});
}

@JS()
@staticInterop
class AudioContext {
external factory AudioContext._([AudioContextOptions? contextOptions]);

factory AudioContext([AudioContextOptions? contextOptions]) =>
AudioContext._(contextOptions ?? undefined);
}

extension PropsBaseAudioContext on AudioContext {
AudioContextState get state =>
AudioContextState.fromValue(jsutil.getProperty(this, 'state'));
}
10 changes: 4 additions & 6 deletions lib/src/track/web/_audio_html.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,18 @@
// limitations under the License.

import 'dart:js_interop';
import 'dart:js_util' as jsutil;
import 'dart:js_interop_unsafe';

import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc;
import 'package:web/web.dart' as web;

import '_audio_context.dart';

// ignore: implementation_imports
import 'package:dart_webrtc/src/media_stream_track_impl.dart'; // import_sorter: keep

const audioContainerId = 'livekit_audio_container';
const audioPrefix = 'livekit_audio_';

AudioContext _audioContext = AudioContext();
web.AudioContext _audioContext = web.AudioContext();
Map<String, web.Element> _audioElements = {};

Future<dynamic> startAudio(String id, rtc.MediaStreamTrack track) async {
Expand Down Expand Up @@ -59,7 +57,7 @@ Future<bool> startAllAudioElement() async {
await element.play().toDart;
}
}
return _audioContext.state == AudioContextState.running;
return _audioContext.state == 'running';
}

void stopAudio(String id) {
Expand Down Expand Up @@ -89,7 +87,7 @@ web.HTMLDivElement findOrCreateAudioContainer() {
void setSinkId(String id, String deviceId) {
final audioElement = web.document.getElementById(audioPrefix + id);
if (audioElement is web.HTMLAudioElement &&
jsutil.hasProperty(audioElement, 'setSinkId')) {
audioElement.hasProperty('setSinkId'.toJS).toDart) {
audioElement.setSinkId(deviceId);
}
}
4 changes: 2 additions & 2 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -590,10 +590,10 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
url: "https://pub.dev"
source: hosted
version: "14.2.4"
version: "14.2.5"
watcher:
dependency: transitive
description:
Expand Down
3 changes: 0 additions & 3 deletions web/README.md

This file was deleted.

103 changes: 4 additions & 99 deletions web/crypto.dart
Original file line number Diff line number Diff line change
@@ -1,108 +1,13 @@
// Copyright 2024 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import 'dart:async';
import 'dart:js_util' as jsutil;
import 'dart:js_interop';
import 'dart:typed_data';

import 'package:js/js.dart';
import 'package:web/web.dart' as web;

@JS('Promise')
class Promise<T> {
external factory Promise._();
}

@JS('Algorithm')
class Algorithm {
external String get name;
}

@JS('crypto.subtle.encrypt')
external Promise<ByteBuffer> encrypt(
dynamic algorithm,
web.CryptoKey key,
ByteBuffer data,
);

@JS('crypto.subtle.decrypt')
external Promise<ByteBuffer> decrypt(
dynamic algorithm,
web.CryptoKey key,
ByteBuffer data,
);

@JS()
@anonymous
class AesGcmParams {
external factory AesGcmParams({
required String name,
required ByteBuffer iv,
ByteBuffer? additionalData,
int tagLength = 128,
});
}

ByteBuffer jsArrayBufferFrom(List<int> data) {
JSArrayBuffer jsArrayBufferFrom(ByteData data) {
// Avoid copying if possible
if (data is Uint8List &&
data.offsetInBytes == 0 &&
data.lengthInBytes == data.buffer.lengthInBytes) {
return data.buffer;
return data.buffer.toJS;
}
// Copy
return Uint8List.fromList(data).buffer;
}

@JS('crypto.subtle.importKey')
external Promise<web.CryptoKey> importKey(
String format,
ByteBuffer keyData,
dynamic algorithm,
bool extractable,
List<String> keyUsages,
);

@JS('crypto.subtle.exportKey')
external Promise<ByteBuffer> exportKey(
String format,
web.CryptoKey key,
);

@JS('crypto.subtle.deriveKey')
external Promise<web.CryptoKey> deriveKey(
dynamic algorithm,
web.CryptoKey baseKey,
dynamic derivedKeyAlgorithm,
bool extractable,
List<String> keyUsages);

@JS('crypto.subtle.deriveBits')
external Promise<ByteBuffer> deriveBits(
dynamic algorithm,
web.CryptoKey baseKey,
int length,
);

Future<web.CryptoKey> impportKeyFromRawData(List<int> secretKeyData,
{required String webCryptoAlgorithm,
required List<String> keyUsages}) async {
return jsutil.promiseToFuture<web.CryptoKey>(importKey(
'raw',
jsArrayBufferFrom(secretKeyData),
jsutil.jsify({'name': webCryptoAlgorithm}),
false,
keyUsages,
));
return Uint8List.fromList(Uint8List.sublistView(data)).buffer.toJS;
}
Loading
Loading