Skip to content

Commit

Permalink
wip.
Browse files Browse the repository at this point in the history
  • Loading branch information
cloudwebrtc committed Nov 19, 2024
1 parent 64dae3c commit ced2713
Show file tree
Hide file tree
Showing 18 changed files with 245 additions and 18 deletions.
1 change: 1 addition & 0 deletions ios/Classes/AudioTrack.swift
1 change: 1 addition & 0 deletions ios/Classes/LocalAudioTrack.swift
1 change: 1 addition & 0 deletions ios/Classes/RemoteAudioTrack.swift
1 change: 1 addition & 0 deletions ios/Classes/Track.swift
13 changes: 13 additions & 0 deletions lib/src/events.dart
Original file line number Diff line number Diff line change
Expand Up @@ -565,3 +565,16 @@ class VideoReceiverStatsEvent with TrackEvent {
String toString() => '${runtimeType}'
'stats: ${stats})';
}

class AudioVisualizerEvent with TrackEvent {
final Track track;
final List<Object?> event;
const AudioVisualizerEvent({
required this.track,
required this.event,
});

@override
String toString() => '${runtimeType}'
'track: ${track})';
}
1 change: 1 addition & 0 deletions lib/src/track/local/local.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc;
import 'package:meta/meta.dart';
Expand Down
1 change: 0 additions & 1 deletion lib/src/track/remote/audio.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import '../../internal/events.dart';
import '../../logger.dart';
import '../../stats/audio_source_stats.dart';
import '../../stats/stats.dart';
import '../../support/native.dart';
import '../../types/other.dart';
import '../audio_management.dart';
import '../local/local.dart';
Expand Down
26 changes: 26 additions & 0 deletions lib/src/track/track.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import 'dart:async';

import 'package:flutter/services.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc;
import 'package:meta/meta.dart';
import 'package:uuid/uuid.dart';
Expand Down Expand Up @@ -111,6 +112,7 @@ abstract class Track extends DisposableChangeNotifier

if(source == TrackSource.microphone) {
await Native.startVisualizer(mediaStreamTrack.id!);
listenVisualizerEvent();
}

startMonitor();
Expand All @@ -130,6 +132,7 @@ abstract class Track extends DisposableChangeNotifier

if(source == TrackSource.microphone) {
await Native.stopVisualizer(mediaStreamTrack.id!);
stopVisualizerEventListen();
}

stopMonitor();
Expand All @@ -140,6 +143,29 @@ abstract class Track extends DisposableChangeNotifier
return true;
}


EventChannel? _eventChannel ;
StreamSubscription? _streamSubscription;

@internal
void listenVisualizerEvent() {
_eventChannel = EventChannel('io.livekit.audio.visualizer/eventChannel-${mediaStreamTrack.id}');

_eventChannel?.receiveBroadcastStream().listen((event) {
logger.fine('[$objectId] visualizer event(${event})');
events.emit(AudioVisualizerEvent(
track: this,
event: event,
));
});
}

@internal
void stopVisualizerEventListen() {
_streamSubscription?.cancel();
_eventChannel = null;
}

Future<void> enable() async {
logger.fine('$objectId.enable() enabling ${mediaStreamTrack.objectId}...');
try {
Expand Down
1 change: 1 addition & 0 deletions macos/Classes/AudioTrack.swift
1 change: 1 addition & 0 deletions macos/Classes/LocalAudioTrack.swift
1 change: 1 addition & 0 deletions macos/Classes/RemoteAudioTrack.swift
1 change: 1 addition & 0 deletions macos/Classes/Track.swift
28 changes: 28 additions & 0 deletions shared_swift/AudioTrack.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2024 LiveKit
*
* 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 Foundation
import WebRTC

@objc
public protocol AudioTrack where Self: Track {

@objc(addAudioRenderer:)
func add(audioRenderer: RTCAudioRenderer)

@objc(removeAudioRenderer:)
func remove(audioRenderer: RTCAudioRenderer)
}
34 changes: 24 additions & 10 deletions shared_swift/LiveKitPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ import UIKit
#endif

public class LiveKitPlugin: NSObject, FlutterPlugin {

var processers: Dictionary<Track, AudioProcessor> = [:]

private var processor: AudioProcessor? = nil
var binaryMessenger: FlutterBinaryMessenger?

public static func register(with registrar: FlutterPluginRegistrar) {

Expand All @@ -37,6 +39,7 @@ public class LiveKitPlugin: NSObject, FlutterPlugin {

let channel = FlutterMethodChannel(name: "livekit_client", binaryMessenger: messenger)
let instance = LiveKitPlugin()
instance.binaryMessenger = messenger
registrar.addMethodCallDelegate(instance, channel: channel)
}

Expand Down Expand Up @@ -94,24 +97,35 @@ public class LiveKitPlugin: NSObject, FlutterPlugin {
let trackId = args["trackId"] as? String

if let unwrappedTrackId = trackId {
let track = webrtc?.track(forId: unwrappedTrackId, peerConnectionId: nil)

let localTrack = webrtc?.localTracks![unwrappedTrackId]
if let audioTrack = localTrack as? LocalAudioTrack {
let lkLocalTrack = LKLocalAudioTrack(name: unwrappedTrackId, track: audioTrack);
let processor = AudioProcessor(track: lkLocalTrack, binaryMessenger: self.binaryMessenger!)
processers[lkLocalTrack] = processor
}

let track = webrtc?.remoteTrack(forId: unwrappedTrackId)
if let audioTrack = track as? RTCAudioTrack {
if processor == nil {
processor = AudioProcessor(track: audioTrack)
}
let lkLocalTrack = LKRemoteAudioTrack(name: unwrappedTrackId, track: audioTrack);
let processor = AudioProcessor(track: lkLocalTrack, binaryMessenger: self.binaryMessenger!)
processers[lkLocalTrack] = processor
}
}

let audioManager = webrtc?.audioManager

if audioManager != nil && processor != nil {
//audioManager?.addLocalAudioRenderer(processor!)
}

result(true)
}

public func handleStopAudioVisualizer(args: [String: Any?], result: @escaping FlutterResult) {
let trackId = args["trackId"] as? String
if let unwrappedTrackId = trackId {
for key in processers.keys {
if key.mediaTrack.trackId == unwrappedTrackId {
processers.removeValue(forKey: key)
}
}
}
result(true)
}

Expand Down
39 changes: 39 additions & 0 deletions shared_swift/LocalAudioTrack.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2024 LiveKit
*
* 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 Foundation
import WebRTC
import flutter_webrtc

public class LKLocalAudioTrack: Track, AudioTrack {
let audioTrack: LocalAudioTrack
init(name: String,
track: LocalAudioTrack)
{
audioTrack = track
super.init(track: track.track())
}
}

public extension LKLocalAudioTrack {
func add(audioRenderer: RTCAudioRenderer) {
audioTrack.add(audioRenderer)
}

func remove(audioRenderer: RTCAudioRenderer) {
audioTrack.remove(audioRenderer)
}
}
42 changes: 42 additions & 0 deletions shared_swift/RemoteAudioTrack.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 2024 LiveKit
*
* 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 Foundation
import WebRTC

import Foundation
import WebRTC
import flutter_webrtc

public class LKRemoteAudioTrack: Track, AudioTrack {
let audioTrack: RTCAudioTrack
init(name: String,
track: RTCAudioTrack)
{
audioTrack = track
super.init(track: track)
}
}

public extension LKRemoteAudioTrack {
func add(audioRenderer: RTCAudioRenderer) {
audioTrack.add(audioRenderer)
}

func remove(audioRenderer: RTCAudioRenderer) {
audioTrack.remove(audioRenderer)
}
}
30 changes: 30 additions & 0 deletions shared_swift/Track.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2024 LiveKit
*
* 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 Foundation
import WebRTC

@objc
public class Track: NSObject {

let mediaTrack: RTCMediaStreamTrack

init(track: RTCMediaStreamTrack)
{
mediaTrack = track
super.init()
}
}
41 changes: 34 additions & 7 deletions shared_swift/Visualizer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,32 +17,59 @@
import AVFoundation
import WebRTC

public class AudioProcessor: NSObject, RTCAudioRenderer {
#if os(macOS)
import Cocoa
import FlutterMacOS
#else
import Flutter
import UIKit
#endif

public class AudioProcessor: NSObject, RTCAudioRenderer, FlutterStreamHandler {

private var eventSink: FlutterEventSink?

private var channel: FlutterEventChannel?

public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
self.eventSink = events
return nil
}

public func onCancel(withArguments arguments: Any?) -> FlutterError? {
eventSink = nil
return nil
}

public let isCentered: Bool
public let smoothingFactor: Float

public var bands: [Float]

private let _processor: AudioVisualizeProcessor
private weak var _track: RTCAudioTrack?
private weak var _track: AudioTrack?

public init(track: RTCAudioTrack?,
public init(track: AudioTrack?,
binaryMessenger: FlutterBinaryMessenger,
bandCount: Int = 7,
isCentered: Bool = true,
smoothingFactor: Float = 0.3)
{
self.isCentered = isCentered
self.smoothingFactor = smoothingFactor
bands = Array(repeating: 0.0, count: bandCount)

_processor = AudioVisualizeProcessor(bandsCount: bandCount)
_track = track
super.init()
_track?.add(self)
_track?.add(audioRenderer: self)
let channelName = "io.livekit.audio.visualizer/eventChannel-" + (track?.mediaTrack.trackId ?? "")
channel = FlutterEventChannel(name: channelName, binaryMessenger: binaryMessenger)
channel?.setStreamHandler(self)
}

deinit {
_track?.remove(self)
_track?.remove(audioRenderer: self)
channel?.setStreamHandler(nil)
}

public func render(pcmBuffer: AVAudioPCMBuffer) {
Expand All @@ -57,7 +84,7 @@ public class AudioProcessor: NSObject, RTCAudioRenderer {

DispatchQueue.main.async { [weak self] in
guard let self else { return }

eventSink?(newBands)
self.bands = zip(self.bands, newBands).map { old, new in
self._smoothTransition(from: old, to: new, factor: self.smoothingFactor)
}
Expand Down

0 comments on commit ced2713

Please sign in to comment.