Skip to content

Commit

Permalink
update to package:web (#484)
Browse files Browse the repository at this point in the history
* update to package:web

* Update pubspec.yaml

* fix issue for web e2ee.

* import_sorter.

* revert license.

---------

Co-authored-by: Jesse Ezell <[email protected]>
Co-authored-by: CloudWebRTC <[email protected]>
  • Loading branch information
3 people authored Apr 8, 2024
1 parent 97e9f50 commit 8ee3341
Show file tree
Hide file tree
Showing 10 changed files with 393 additions and 429 deletions.
3 changes: 2 additions & 1 deletion lib/livekit_client_web.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ import 'package:flutter_web_plugins/flutter_web_plugins.dart';
// package as the core of your plugin.

// ignore: unused_import
import 'dart:html' as html show document, ScriptElement; // import_sorter: keep
import 'package:web/web.dart' as web
show document, HTMLScriptElement; // import_sorter: keep

/// A web implementation of the Livekit plugin.
class LiveKitWebPlugin {
Expand Down
2 changes: 1 addition & 1 deletion lib/src/internal/events.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import '../track/options.dart';
import '../track/track.dart';
import '../types/other.dart';

abstract class InternalEvent implements LiveKitEvent {}
mixin InternalEvent implements LiveKitEvent {}

@internal
abstract class EnginePeerStateUpdatedEvent with EngineEvent, InternalEvent {
Expand Down
15 changes: 9 additions & 6 deletions lib/src/support/websocket/web.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@
// limitations under the License.

import 'dart:async';
import 'dart:html' as html;
import 'dart:js_interop';
import 'dart:typed_data';

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

import '../../extensions.dart';
import '../../logger.dart';
import '../websocket.dart';
Expand All @@ -29,7 +31,7 @@ Future<LiveKitWebSocketWeb> lkWebSocketConnect(
LiveKitWebSocketWeb.connect(uri, options);

class LiveKitWebSocketWeb extends LiveKitWebSocket {
final html.WebSocket _ws;
final web.WebSocket _ws;
final WebSocketEventHandlers? options;
late final StreamSubscription _messageSubscription;
late final StreamSubscription _closeSubscription;
Expand All @@ -44,7 +46,8 @@ class LiveKitWebSocketWeb extends LiveKitWebSocket {
logger.warning('$objectId already disposed, ignoring received data.');
return;
}
dynamic data = _.data is ByteBuffer ? _.data.asUint8List() : _.data;
dynamic data =
_.data is ByteBuffer ? (_.data as ByteBuffer).asUint8List() : _.data;
options?.onData?.call(data);
});
_closeSubscription = _ws.onClose.listen((_) async {
Expand All @@ -54,21 +57,21 @@ class LiveKitWebSocketWeb extends LiveKitWebSocket {
});

onDispose(() async {
if (_ws.readyState != html.WebSocket.CLOSED) {
if (_ws.readyState != web.WebSocket.CLOSED) {
_ws.close();
}
});
}

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

static Future<LiveKitWebSocketWeb> connect(
Uri uri, [
WebSocketEventHandlers? options,
]) async {
final completer = Completer<LiveKitWebSocketWeb>();
final ws = html.WebSocket(uri.toString());
final ws = web.WebSocket(uri.toString());
ws.onOpen
.listen((_) => completer.complete(LiveKitWebSocketWeb._(ws, options)));
ws.onError
Expand Down
43 changes: 24 additions & 19 deletions lib/src/track/web/_audio_html.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.

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

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

import '_audio_context.dart';

Expand All @@ -26,69 +27,73 @@ const audioContainerId = 'livekit_audio_container';
const audioPrefix = 'livekit_audio_';

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

Future<dynamic> startAudio(String id, rtc.MediaStreamTrack track) async {
if (track is! MediaStreamTrackWeb) {
return;
}

final elementId = audioPrefix + id;
var audioElement = html.document.getElementById(elementId);
var audioElement = web.document.getElementById(elementId);
if (audioElement == null) {
audioElement = html.AudioElement()
audioElement = web.HTMLAudioElement()
..id = elementId
..autoplay = true;
findOrCreateAudioContainer().append(audioElement);
_audioElements[id] = audioElement;
}

if (audioElement is! html.AudioElement) {
if (audioElement is! web.HTMLAudioElement) {
return;
}
final audioStream = html.MediaStream();
final audioStream = web.MediaStream();
audioStream.addTrack(track.jsTrack);
audioElement.srcObject = audioStream;
return audioElement.play();
}

Future<bool> startAllAudioElement() async {
for (final element in _audioElements.values) {
if (element is html.AudioElement) {
await element.play();
if (element is web.HTMLAudioElement) {
await element.play().toDart;
}
}
return _audioContext.state == AudioContextState.running;
}

void stopAudio(String id) {
final audioElement = html.document.getElementById(audioPrefix + id);
final audioElement = web.document.getElementById(audioPrefix + id);
if (audioElement != null) {
if (audioElement is html.AudioElement) {
if (audioElement is web.HTMLAudioElement) {
audioElement.srcObject = null;
}
_audioElements.remove(id);
audioElement.remove();
}
}

html.DivElement findOrCreateAudioContainer() {
var div = html.document.getElementById(audioContainerId);
web.HTMLDivElement findOrCreateAudioContainer() {
var div = web.document.getElementById(audioContainerId);
if (div != null) {
return div as html.DivElement;
return div as web.HTMLDivElement;
}

div = html.DivElement();
div = web.HTMLDivElement();
div.id = audioContainerId;
div.style.display = 'none';
html.document.body?.append(div);
return div as html.DivElement;
(div as web.HTMLDivElement).style.display = 'none';
web.document.body?.append(div);
return div;
}

void setSinkId(String id, String deviceId) {
final audioElement = html.document.getElementById(audioPrefix + id);
if (audioElement is html.AudioElement &&
final audioElement = web.document.getElementById(audioPrefix + id);
if (audioElement is web.HTMLAudioElement &&
jsutil.hasProperty(audioElement, 'setSinkId')) {
audioElement.setSinkId(deviceId);
}
}

extension _SetSinkId on web.HTMLMediaElement {
external JSPromise setSinkId(String sinkId);
}
8 changes: 4 additions & 4 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ version: 2.1.0
homepage: https://github.com/livekit/client-sdk-flutter

environment:
sdk: ">=2.17.0 <4.0.0"
sdk: ">=3.3.0 <4.0.0"
flutter: ">=1.17.0"

dependencies:
Expand All @@ -38,11 +38,11 @@ dependencies:
uuid: '>=3.0.6'
synchronized: ^3.0.0+3
protobuf: ^3.0.0
flutter_webrtc: ^0.9.48+hotfix.1
flutter_webrtc: ^0.10.1
device_info_plus: '>=8.0.0'
js: ^0.6.4
platform_detect: ^2.0.7
dart_webrtc: ^1.2.0
dart_webrtc: ^1.3.0
sdp_transform: ^0.3.2

dev_dependencies:
Expand Down Expand Up @@ -81,4 +81,4 @@ topics:
- video
- livestream
- conference


20 changes: 10 additions & 10 deletions web/crypto.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@
// limitations under the License.

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

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

@JS('Promise')
class Promise<T> {
Expand All @@ -32,14 +32,14 @@ class Algorithm {
@JS('crypto.subtle.encrypt')
external Promise<ByteBuffer> encrypt(
dynamic algorithm,
html.CryptoKey key,
web.CryptoKey key,
ByteBuffer data,
);

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

Expand All @@ -66,7 +66,7 @@ ByteBuffer jsArrayBufferFrom(List<int> data) {
}

@JS('crypto.subtle.importKey')
external Promise<html.CryptoKey> importKey(
external Promise<web.CryptoKey> importKey(
String format,
ByteBuffer keyData,
dynamic algorithm,
Expand All @@ -77,28 +77,28 @@ external Promise<html.CryptoKey> importKey(
@JS('crypto.subtle.exportKey')
external Promise<ByteBuffer> exportKey(
String format,
html.CryptoKey key,
web.CryptoKey key,
);

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

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

Future<html.CryptoKey> impportKeyFromRawData(List<int> secretKeyData,
Future<web.CryptoKey> impportKeyFromRawData(List<int> secretKeyData,
{required String webCryptoAlgorithm,
required List<String> keyUsages}) async {
return jsutil.promiseToFuture<html.CryptoKey>(importKey(
return jsutil.promiseToFuture<web.CryptoKey>(importKey(
'raw',
jsArrayBufferFrom(secretKeyData),
jsutil.jsify({'name': webCryptoAlgorithm}),
Expand Down
35 changes: 11 additions & 24 deletions web/e2ee.cryptor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.

// ignore_for_file: constant_identifier_names

import 'dart:async';
import 'dart:html';
import 'dart:js';
import 'dart:js_interop';
import 'dart:js_util' as jsutil;
import 'dart:math';
import 'dart:typed_data';

import 'package:dart_webrtc/src/rtc_transform_stream.dart';
import 'package:web/web.dart' as web;

import 'crypto.dart' as crypto;
import 'e2ee.keyhandler.dart';
import 'e2ee.logger.dart';
Expand Down Expand Up @@ -152,7 +152,7 @@ class FrameCryptor {
bool _enabled = false;
CryptorError lastError = CryptorError.kNew;
int currentKeyIndex = 0;
final DedicatedWorkerGlobalScope worker;
final web.DedicatedWorkerGlobalScope worker;
SifGuard sifGuard = SifGuard();

void setParticipant(String identity, ParticipantKeyHandler keys) {
Expand Down Expand Up @@ -235,7 +235,7 @@ class FrameCryptor {
}

void postMessage(Object message) {
worker.postMessage(message);
worker.postMessage(message.jsify());
}

Future<void> setupTransform({
Expand Down Expand Up @@ -333,8 +333,6 @@ class FrameCryptor {
'kind': kind,
'state': 'missingKey',
'error': 'Missing key for track $trackId',
'currentKeyIndex': currentKeyIndex,
'secretKey': secretKey.toString()
});
}
return;
Expand Down Expand Up @@ -384,10 +382,7 @@ class FrameCryptor {
'trackId': trackId,
'kind': kind,
'state': 'ok',
'error': 'encryption ok',
'frameTrailer': frameTrailer.buffer.asUint8List(),
'currentKeyIndex': currentKeyIndex,
'secretKey': secretKey.toString(),
'error': 'encryption ok'
});
}

Expand Down Expand Up @@ -478,10 +473,7 @@ class FrameCryptor {
'trackId': trackId,
'kind': kind,
'state': 'missingKey',
'error': 'Missing key for track $trackId',
'frameTrailer': frameTrailer.buffer.asUint8List(),
'currentKeyIndex': keyIndex,
'secretKey': initialKeySet?.encryptionKey.toString()
'error': 'Missing key for track $trackId'
});
}
controller.enqueue(frame);
Expand All @@ -504,7 +496,7 @@ class FrameCryptor {
));

if (currentkeySet != initialKeySet) {
logger.warning(
logger.fine(
'ratchetKey: decryption ok, reset state to kKeyRatcheted');
await keyHandler.setKeySetFromMaterial(
currentkeySet, initialKeyIndex);
Expand All @@ -528,10 +520,7 @@ class FrameCryptor {
'trackId': trackId,
'kind': kind,
'state': 'keyRatcheted',
'error': 'Key ratcheted ok',
'frameTrailer': frameTrailer.buffer.asUint8List(),
'currentKeyIndex': currentKeyIndex,
'secretKey': currentkeySet.encryptionKey.toString()
'error': 'Key ratcheted ok'
});
}
} catch (e) {
Expand All @@ -554,6 +543,7 @@ class FrameCryptor {
logger.finer(
'buffer: ${buffer.length}, decrypted: ${decrypted?.asUint8List().length ?? 0}');
var finalBuffer = BytesBuilder();

finalBuffer.add(Uint8List.fromList(buffer.sublist(0, headerLength)));
finalBuffer.add(decrypted!.asUint8List());
frame.data = crypto.jsArrayBufferFrom(finalBuffer.toBytes());
Expand All @@ -568,10 +558,7 @@ class FrameCryptor {
'trackId': trackId,
'kind': kind,
'state': 'ok',
'error': 'decryption ok',
'frameTrailer': frameTrailer.buffer.asUint8List(),
'currentKeyIndex': currentKeyIndex,
'secretKey': currentkeySet.encryptionKey.toString()
'error': 'decryption ok'
});
}

Expand Down
Loading

0 comments on commit 8ee3341

Please sign in to comment.