This commit is contained in:
2025-10-13 10:58:00 +04:00
parent bcfc525bee
commit aa992cef7d
21 changed files with 9 additions and 11 deletions

View File

@@ -0,0 +1,7 @@
!!hero_code.generate_client
name:'livekit'
classname:'LivekitClient'
singleton:0
default:1
active:0

View File

@@ -0,0 +1,152 @@
module livekit
// import time
// import rand
// import crypto.hmac
// import crypto.sha256
// import encoding.base64
// import json
// // Define AccessTokenOptions struct
// pub struct AccessTokenOptions {
// pub mut:
// ttl int | string // TTL in seconds or a time span (e.g., '2d', '5h')
// name string // Display name for the participant
// identity string // Identity of the user
// metadata string // Custom metadata to be passed to participants
// }
// // Struct representing grants
// pub struct ClaimGrants {
// pub mut:
// video VideoGrant
// iss string
// exp i64
// nbf int
// sub string
// name string
// }
// // VideoGrant struct placeholder
// pub struct VideoGrant {
// pub mut:
// room string
// room_join bool @[json: 'roomJoin']
// can_publish bool @[json: 'canPublish']
// can_publish_data bool @[json: 'canPublishData']
// can_subscribe bool @[json: 'canSubscribe']
// }
// // SIPGrant struct placeholder
// struct SIPGrant {}
// // AccessToken class
// pub struct AccessToken {
// mut:
// api_key string
// api_secret string
// grants ClaimGrants
// identity string
// ttl int | string
// }
// // Constructor for AccessToken
// pub fn new_access_token(api_key string, api_secret string, options AccessTokenOptions) !AccessToken {
// if api_key == '' || api_secret == '' {
// return error('API key and API secret must be set')
// }
// ttl := if options.ttl is int { options.ttl } else { 21600 } // Default TTL of 6 hours (21600 seconds)
// return AccessToken{
// api_key: api_key
// api_secret: api_secret
// identity: options.identity
// ttl: ttl
// grants: ClaimGrants{
// exp: time.now().unix()+ttl
// iss: api_key
// sub: options.name
// name: options.name
// }
// }
// }
// // Method to add a video grant to the token
// pub fn (mut token AccessToken) add_video_grant(grant VideoGrant) {
// token.grants.video = grant
// }
// // Method to generate a JWT token
// pub fn (token AccessToken) to_jwt() !string {
// // Create JWT payload
// payload := json.encode(token.grants)
// println('payload: ${payload}')
// // Create JWT header
// header := '{"alg":"HS256","typ":"JWT"}'
// // Encode header and payload in base64
// header_encoded := base64.url_encode_str(header)
// payload_encoded := base64.url_encode_str(payload)
// // Create the unsigned token
// unsigned_token := '${header_encoded}.${payload_encoded}'
// // Create the HMAC-SHA256 signature
// signature := hmac.new(token.api_secret.bytes(), unsigned_token.bytes(), sha256.sum, sha256.block_size)
// // Encode the signature in base64
// signature_encoded := base64.url_encode(signature)
// // Create the final JWT
// jwt := '${unsigned_token}.${signature_encoded}'
// return jwt
// }
// // TokenVerifier class
// pub struct TokenVerifier {
// api_key string
// api_secret string
// }
// // Constructor for TokenVerifier
// pub fn new_token_verifier(api_key string, api_secret string) !TokenVerifier {
// if api_key == '' || api_secret == '' {
// return error('API key and API secret must be set')
// }
// return TokenVerifier{
// api_key: api_key
// api_secret: api_secret
// }
// }
// // Method to verify the JWT token
// pub fn (verifier TokenVerifier) verify(token string) !ClaimGrants {
// // Split the token into parts
// parts := token.split('.')
// if parts.len != 3 {
// return error('Invalid token')
// }
// // Decode header, payload, and signature
// payload_encoded := parts[1]
// signature_encoded := parts[2]
// // Recompute the HMAC-SHA256 signature
// unsigned_token := '${parts[0]}.${parts[1]}'
// expected_signature := hmac.new(verifier.api_secret.bytes(), unsigned_token.bytes(), sha256.sum, sha256.block_size)
// expected_signature_encoded := base64.url_encode(expected_signature)
// // Verify the signature
// if signature_encoded != expected_signature_encoded {
// return error('Invalid token signature')
// }
// // Decode the payload
// payload_json := base64.url_decode_str(payload_encoded)
// // Parse and return the claims as ClaimGrants
// return json.decode(ClaimGrants, payload_json)
// }

View File

@@ -0,0 +1,37 @@
module livekit
import net.http
import json
import time
fn (mut c LivekitClient) post(path string, body any) !http.Response {
mut token := c.new_access_token(
identity: 'api'
name: 'API User'
ttl: 10 * 60 // 10 minutes
)!
token.add_video_grant(VideoGrant{
room_create: true
room_admin: true
room_list: true
})
jwt := token.to_jwt()!
mut header := http.new_header()
header.add('Authorization', 'Bearer ' + jwt)!
header.add('Content-Type', 'application/json')!
url := '${c.url}/${path}'
data := json.encode(body)
mut req := http.Request{
method: .post
url: url
header: header
data: data
}
resp := http.fetch(req)!
if resp.status_code != 200 {
return error('failed to execute request: ${resp.body}')
}
return resp
}

View File

@@ -0,0 +1,37 @@
module livekit
// import incubaid.herolib.data.caching
import os
// const CACHING_METHOD = caching.CachingMethod.once_per_process
fn _init() ! {
if caching.is_set(key: 'livekit_clients') {
return
}
caching.set[map[string]LivekitClient](
key: 'livekit_clients'
val: map[string]LivekitClient{}
)!
}
fn _get() !map[string]LivekitClient {
_init()!
return caching.get[map[string]LivekitClient](key: 'livekit_clients')!
}
pub fn get(name string) !LivekitClient {
mut clients := _get()!
return clients[name] or { return error('livekit client ${name} not found') }
}
pub fn set(client LivekitClient) ! {
mut clients := _get()!
clients[client.name] = client
caching.set[map[string]LivekitClient](key: 'livekit_clients', val: clients)!
}
pub fn exists(name string) !bool {
mut clients := _get()!
return name in clients
}

View File

@@ -0,0 +1,18 @@
module livekit
pub struct SendDataArgs {
pub mut:
room_name string
data []u8
kind DataPacket_Kind
destination_sids []string
}
pub enum DataPacket_Kind {
reliable
lossy
}
pub fn (mut c LivekitClient) send_data(args SendDataArgs) ! {
_ = c.post('twirp/livekit.RoomService/SendData', args)!
}

View File

@@ -0,0 +1,95 @@
module livekit
import json
pub struct EgressInfo {
pub mut:
egress_id string
room_id string
status string
started_at i64
ended_at i64
error string
}
pub struct StartRoomCompositeEgressArgs {
pub mut:
room_name string
layout string
audio_only bool
video_only bool
custom_base_url string
}
pub struct StartTrackCompositeEgressArgs {
pub mut:
room_name string
audio_track_id string
video_track_id string
}
pub struct StartWebEgressArgs {
pub mut:
url string
audio_only bool
video_only bool
}
pub struct UpdateStreamArgs {
pub mut:
add_output_urls []string
remove_output_urls []string
}
pub fn (mut c LivekitClient) start_room_composite_egress(args StartRoomCompositeEgressArgs) !EgressInfo {
mut resp := c.post('twirp/livekit.Egress/StartRoomCompositeEgress', args)!
egress_info := json.decode[EgressInfo](resp.body)!
return egress_info
}
pub fn (mut c LivekitClient) start_track_composite_egress(args StartTrackCompositeEgressArgs) !EgressInfo {
mut resp := c.post('twirp/livekit.Egress/StartTrackCompositeEgress', args)!
egress_info := json.decode[EgressInfo](resp.body)!
return egress_info
}
pub fn (mut c LivekitClient) start_web_egress(args StartWebEgressArgs) !EgressInfo {
mut resp := c.post('twirp/livekit.Egress/StartWebEgress', args)!
egress_info := json.decode[EgressInfo](resp.body)!
return egress_info
}
pub fn (mut c LivekitClient) update_layout(egress_id string, layout string) !EgressInfo {
mut resp := c.post('twirp/livekit.Egress/UpdateLayout', {
'egress_id': egress_id
'layout': layout
})!
egress_info := json.decode[EgressInfo](resp.body)!
return egress_info
}
pub fn (mut c LivekitClient) update_stream(egress_id string, args UpdateStreamArgs) !EgressInfo {
mut resp := c.post('twirp/livekit.Egress/UpdateStream', {
'egress_id': egress_id
'add_output_urls': args.add_output_urls
'remove_output_urls': args.remove_output_urls
})!
egress_info := json.decode[EgressInfo](resp.body)!
return egress_info
}
pub fn (mut c LivekitClient) list_egress(room_name string) ![]EgressInfo {
mut resp := c.post('twirp/livekit.Egress/ListEgress', {
'room_name': room_name
})!
egress_infos := json.decode[[]EgressInfo](resp.body)!
return egress_infos
}
pub fn (mut c LivekitClient) stop_egress(egress_id string) !EgressInfo {
mut resp := c.post('twirp/livekit.Egress/StopEgress', {
'egress_id': egress_id
})!
egress_info := json.decode[EgressInfo](resp.body)!
return egress_info
}

View File

@@ -0,0 +1,7 @@
module livekit
pub fn new(client Client) Client {
return Client{
...client
}
}

View File

@@ -0,0 +1,141 @@
module livekit
import json
pub struct IngressInfo {
pub mut:
ingress_id string
name string
stream_key string
url string
input_type IngressInput
audio IngressAudioOptions
video IngressVideoOptions
state IngressState
}
pub enum IngressInput {
rtmp_input
whip_input
}
pub struct IngressAudioOptions {
pub mut:
name string
source TrackSource
preset AudioPreset
}
pub struct IngressVideoOptions {
pub mut:
name string
source TrackSource
preset VideoPreset
}
pub enum TrackSource {
camera
microphone
screen_share
screen_share_audio
}
pub enum AudioPreset {
opus_stereo_96kbps
opus_mono_64kbps
}
pub enum VideoPreset {
h264_720p_30fps_3mbps
h264_1080p_30fps_4_5mbps
h264_540p_25fps_2mbps
}
pub struct IngressState {
pub mut:
status IngressStatus
error string
video InputVideoState
audio InputAudioState
room_id string
started_at i64
}
pub enum IngressStatus {
endpoint_inactive
endpoint_buffering
endpoint_publishing
}
pub struct InputVideoState {
pub mut:
mime_type string
width u32
height u32
framerate u32
}
pub struct InputAudioState {
pub mut:
mime_type string
channels u32
sample_rate u32
}
pub struct CreateIngressArgs {
pub mut:
name string
room_name string
participant_identity string
participant_name string
input_type IngressInput
audio IngressAudioOptions
video IngressVideoOptions
}
pub fn (mut c LivekitClient) create_ingress(args CreateIngressArgs) !IngressInfo {
mut resp := c.post('twirp/livekit.Ingress/CreateIngress', args)!
ingress_info := json.decode[IngressInfo](resp.body)!
return ingress_info
}
pub struct UpdateIngressArgs {
pub mut:
ingress_id string
name string
room_name string
participant_identity string
participant_name string
audio IngressAudioOptions
video IngressVideoOptions
}
pub fn (mut c LivekitClient) update_ingress(args UpdateIngressArgs) !IngressInfo {
mut resp := c.post('twirp/livekit.Ingress/UpdateIngress', {
'ingress_id': args.ingress_id
'name': args.name
'room_name': args.room_name
'participant_identity': args.participant_identity
'participant_name': args.participant_name
'audio': args.audio
'video': args.video
})!
ingress_info := json.decode[IngressInfo](resp.body)!
return ingress_info
}
pub fn (mut c LivekitClient) list_ingress(room_name string) ![]IngressInfo {
mut resp := c.post('twirp/livekit.Ingress/ListIngress', {
'room_name': room_name
})!
ingress_infos := json.decode[[]IngressInfo](resp.body)!
return ingress_infos
}
pub fn (mut c LivekitClient) delete_ingress(ingress_id string) !IngressInfo {
mut resp := c.post('twirp/livekit.Ingress/DeleteIngress', {
'ingress_id': ingress_id
})!
ingress_info := json.decode[IngressInfo](resp.body)!
return ingress_info
}

View File

@@ -0,0 +1,140 @@
module livekit
import incubaid.herolib.core.base
import incubaid.herolib.core.playbook { PlayBook }
import incubaid.herolib.ui.console
import json
__global (
livekit_global map[string]&LivekitClient
livekit_default string
)
/////////FACTORY
@[params]
pub struct ArgsGet {
pub mut:
name string = 'default'
fromdb bool // will load from filesystem
create bool // default will not create if not exist
}
pub fn new(args ArgsGet) !&LivekitClient {
mut obj := LivekitClient{
name: args.name
}
set(obj)!
return get(name: args.name)!
}
pub fn get(args ArgsGet) !&LivekitClient {
mut context := base.context()!
livekit_default = args.name
if args.fromdb || args.name !in livekit_global {
mut r := context.redis()!
if r.hexists('context:livekit', args.name)! {
data := r.hget('context:livekit', args.name)!
if data.len == 0 {
print_backtrace()
return error('LivekitClient with name: livekit does not exist, prob bug.')
}
mut obj := json.decode(LivekitClient, data)!
set_in_mem(obj)!
} else {
if args.create {
new(args)!
} else {
print_backtrace()
return error("LivekitClient with name 'livekit' does not exist")
}
}
return get(name: args.name)! // no longer from db nor create
}
return livekit_global[args.name] or {
print_backtrace()
return error('could not get config for livekit with name:livekit')
}
}
// register the config for the future
pub fn set(o LivekitClient) ! {
mut o2 := set_in_mem(o)!
livekit_default = o2.name
mut context := base.context()!
mut r := context.redis()!
r.hset('context:livekit', o2.name, json.encode(o2))!
}
// does the config exists?
pub fn exists(args ArgsGet) !bool {
mut context := base.context()!
mut r := context.redis()!
return r.hexists('context:livekit', args.name)!
}
pub fn delete(args ArgsGet) ! {
mut context := base.context()!
mut r := context.redis()!
r.hdel('context:livekit', args.name)!
}
@[params]
pub struct ArgsList {
pub mut:
fromdb bool // will load from filesystem
}
// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem
pub fn list(args ArgsList) ![]&LivekitClient {
mut res := []&LivekitClient{}
mut context := base.context()!
if args.fromdb {
// reset what is in mem
livekit_global = map[string]&LivekitClient{}
livekit_default = ''
}
if args.fromdb {
mut r := context.redis()!
mut l := r.hkeys('context:livekit')!
for name in l {
res << get(name: name, fromdb: true)!
}
return res
} else {
// load from memory
for _, client in livekit_global {
res << client
}
}
return res
}
// only sets in mem, does not set as config
fn set_in_mem(o LivekitClient) !LivekitClient {
mut o2 := obj_init(o)!
livekit_global[o2.name] = &o2
livekit_default = o2.name
return o2
}
pub fn play(mut plbook PlayBook) ! {
if !plbook.exists(filter: 'livekit.') {
return
}
mut install_actions := plbook.find(filter: 'livekit.configure')!
if install_actions.len > 0 {
for mut install_action in install_actions {
heroscript := install_action.heroscript()
mut obj2 := heroscript_loads(heroscript)!
set(obj2)!
install_action.done = true
}
}
}
// switch instance to be used for livekit
pub fn switch(name string) {
livekit_default = name
}

View File

@@ -0,0 +1,44 @@
module livekit
import incubaid.herolib.data.paramsparser
import incubaid.herolib.data.encoderhero
import os
pub const version = '0.0.0'
const singleton = false
const default = true
@[heap]
pub struct LivekitClient {
pub mut:
name string = 'default'
url string @[required]
api_key string @[required]
api_secret string @[required; secret]
}
// your checking & initialization code if needed
fn obj_init(mycfg_ LivekitClient) !LivekitClient {
mut mycfg := mycfg_
if mycfg.url == '' {
return error('url needs to be filled in for ${mycfg.name}')
}
if mycfg.api_key == '' {
return error('api_key needs to be filled in for ${mycfg.name}')
}
if mycfg.api_secret == '' {
return error('api_secret needs to be filled in for ${mycfg.name}')
}
return mycfg
}
/////////////NORMALLY NO NEED TO TOUCH
pub fn heroscript_dumps(obj LivekitClient) !string {
return encoderhero.encode[LivekitClient](obj)!
}
pub fn heroscript_loads(heroscript string) !LivekitClient {
mut obj := encoderhero.decode[LivekitClient](heroscript)!
return obj
}

View File

@@ -0,0 +1,65 @@
module livekit
import json
pub struct ParticipantInfo {
pub mut:
sid string
identity string
state string
metadata string
joined_at i64
name string
version u32
permission string
region string
publisher bool
}
pub struct UpdateParticipantArgs {
pub mut:
room_name string
identity string
metadata string
permission string
}
pub struct MutePublishedTrackArgs {
pub mut:
room_name string
identity string
track_sid string
muted bool
}
pub fn (mut c LivekitClient) list_participants(room_name string) ![]ParticipantInfo {
mut resp := c.post('twirp/livekit.RoomService/ListParticipants', {
'room': room_name
})!
participants := json.decode[[]ParticipantInfo](resp.body)!
return participants
}
pub fn (mut c LivekitClient) get_participant(room_name string, identity string) !ParticipantInfo {
mut resp := c.post('twirp/livekit.RoomService/GetParticipant', {
'room': room_name
'identity': identity
})!
participant := json.decode[ParticipantInfo](resp.body)!
return participant
}
pub fn (mut c LivekitClient) remove_participant(room_name string, identity string) ! {
_ = c.post('twirp/livekit.RoomService/RemoveParticipant', {
'room': room_name
'identity': identity
})!
}
pub fn (mut c LivekitClient) update_participant(args UpdateParticipantArgs) ! {
_ = c.post('twirp/livekit.RoomService/UpdateParticipant', args)!
}
pub fn (mut c LivekitClient) mute_published_track(args MutePublishedTrackArgs) ! {
_ = c.post('twirp/livekit.RoomService/MutePublishedTrack', args)!
}

View File

@@ -0,0 +1,167 @@
module livekit
import incubaid.herolib.core.playbook { PlayBook }
import incubaid.herolib.core.texttools
import incubaid.herolib.ui.console
pub fn play(mut plbook PlayBook) ! {
if !plbook.exists(filter: 'livekit.') {
return
}
// Handle livekit.init - configure the client
if plbook.exists_once(filter: 'livekit.init') {
mut action := plbook.get(filter: 'livekit.init')!
mut p := action.params
name := texttools.name_fix(p.get_default('name', 'default')!)
url := p.get('url')!
api_key := p.get('api_key')!
api_secret := p.get('api_secret')!
mut client := LivekitClient{
name: name
url: url
api_key: api_key
api_secret: api_secret
}
set(client)!
console.print_header('LiveKit client "${name}" configured')
action.done = true
}
// Handle room creation
mut room_create_actions := plbook.find(filter: 'livekit.room_create')!
for mut action in room_create_actions {
mut p := action.params
client_name := texttools.name_fix(p.get_default('client', 'default')!)
room_name := p.get('name')!
empty_timeout := p.get_u32_default('empty_timeout', 300)!
max_participants := p.get_u32_default('max_participants', 50)!
metadata := p.get_default('metadata', '')!
mut client := get(name: client_name)!
room := client.create_room(
name: room_name
empty_timeout: empty_timeout
max_participants: max_participants
metadata: metadata
)!
console.print_header('Room "${room_name}" created successfully')
action.done = true
}
// Handle room deletion
mut room_delete_actions := plbook.find(filter: 'livekit.room_delete')!
for mut action in room_delete_actions {
mut p := action.params
client_name := texttools.name_fix(p.get_default('client', 'default')!)
room_name := p.get('name')!
mut client := get(name: client_name)!
client.delete_room(room_name)!
console.print_header('Room "${room_name}" deleted successfully')
action.done = true
}
// Handle participant removal
mut participant_remove_actions := plbook.find(filter: 'livekit.participant_remove')!
for mut action in participant_remove_actions {
mut p := action.params
client_name := texttools.name_fix(p.get_default('client', 'default')!)
room_name := p.get('room')!
identity := p.get('identity')!
mut client := get(name: client_name)!
client.remove_participant(room_name, identity)!
console.print_header('Participant "${identity}" removed from room "${room_name}"')
action.done = true
}
// Handle participant mute/unmute
mut participant_mute_actions := plbook.find(filter: 'livekit.participant_mute')!
for mut action in participant_mute_actions {
mut p := action.params
client_name := texttools.name_fix(p.get_default('client', 'default')!)
room_name := p.get('room')!
identity := p.get('identity')!
track_sid := p.get('track_sid')!
muted := p.get_default_true('muted')
mut client := get(name: client_name)!
client.mute_published_track(
room_name: room_name
identity: identity
track_sid: track_sid
muted: muted
)!
status := if muted { 'muted' } else { 'unmuted' }
console.print_header('Track "${track_sid}" ${status} for participant "${identity}"')
action.done = true
}
// Handle room metadata update
mut room_update_actions := plbook.find(filter: 'livekit.room_update')!
for mut action in room_update_actions {
mut p := action.params
client_name := texttools.name_fix(p.get_default('client', 'default')!)
room_name := p.get('room')!
metadata := p.get('metadata')!
mut client := get(name: client_name)!
client.update_room_metadata(
room_name: room_name
metadata: metadata
)!
console.print_header('Room "${room_name}" metadata updated')
action.done = true
}
// Handle access token generation
mut token_create_actions := plbook.find(filter: 'livekit.token_create')!
// for mut action in token_create_actions {
// mut p := action.params
// client_name := texttools.name_fix(p.get_default('client', 'default')!)
// identity := p.get('identity')!
// name := p.get_default('name', identity)!
// room := p.get_default('room', '')!
// ttl := p.get_int_default('ttl', 21600)!
// can_publish := p.get_default_false('can_publish')
// can_subscribe := p.get_default_true('can_subscribe')
// can_publish_data := p.get_default_false('can_publish_data')
// mut client := get(name: client_name)!
// mut token := client.new_access_token(
// identity: identity
// name: name
// ttl: ttl
// )!
// token.add_video_grant(VideoGrant{
// room: room
// room_join: true
// can_publish: can_publish
// can_subscribe: can_subscribe
// can_publish_data: can_publish_data
// })
// jwt := token.to_jwt()!
// console.print_header('Access token generated for "${identity}"')
// console.print_debug('Token: ${jwt}')
// action.done = true
// }
}

View File

@@ -0,0 +1,22 @@
# livekit
To get started
```v
import incubaid.herolib.clients.livekit
mut client:= livekit.get()!
client...
```
## example heroscript
```hero
!!livekit.configure
livekit_url:''
livekit_api_key:''
livekit_api_secret:''
```

View File

@@ -0,0 +1,49 @@
module livekit
import json
import net.http
pub struct Room {
pub mut:
sid string
name string
empty_timeout u32
max_participants u32
creation_time i64
turn_password string
enabled_codecs []string
metadata string
num_participants u32
num_connected_participants u32
active_recording bool
}
pub struct CreateRoomArgs {
pub mut:
name string
empty_timeout u32
max_participants u32
metadata string
}
pub struct UpdateRoomMetadataArgs {
pub mut:
room_name string
metadata string
}
pub fn (mut c LivekitClient) create_room(args CreateRoomArgs) !Room {
mut resp := c.post('twirp/livekit.RoomService/CreateRoom', args)!
room := json.decode[Room](resp.body)!
return room
}
pub fn (mut c LivekitClient) delete_room(room_name string) ! {
_ = c.post('twirp/livekit.RoomService/DeleteRoom', {
'room': room_name
})!
}
pub fn (mut c LivekitClient) update_room_metadata(args UpdateRoomMetadataArgs) ! {
_ = c.post('twirp/livekit.RoomService/UpdateRoomMetadata', args)!
}

View File

@@ -0,0 +1,33 @@
module livekit
import net.http
import json
pub struct Codec {
pub:
fmtp_line string
mime string
}
pub struct Version {
pub:
ticks u64
unix_micro string
}
// pub struct Room {
// pub:
// active_recording bool
// creation_time string
// departure_timeout int
// empty_timeout int
// enabled_codecs []Codec
// max_participants int
// metadata string
// name string
// num_participants int
// num_publishers int
// sid string
// turn_password string
// version Version
// }

View File

@@ -0,0 +1,25 @@
module livekit
import os
import incubaid.herolib.osal.core as osal
const env_file = '${os.dir(@FILE)}/.env'
fn testsuite_begin() ! {
if os.exists(env_file) {
osal.load_env_file(env_file)!
}
}
fn new_test_client() Client {
return new(
url: os.getenv('LIVEKIT_URL')
api_key: os.getenv('LIVEKIT_API_KEY')
api_secret: os.getenv('LIVEKIT_API_SECRET')
)
}
fn test_client_list_rooms() ! {
client := new_test_client()
rooms := client.list_rooms()!
}

View File

@@ -0,0 +1,199 @@
module livekit
import net.http
import json
// // pub struct Client {
// // pub:
// // host string
// // token string
// // }
// // pub struct Room {
// // pub mut:
// // sid string
// // name string
// // empty_timeout string
// // max_participants string
// // creation_time string
// // turn_password string
// // metadata string
// // num_participants u32
// // active_recording bool
// // }
// pub struct ParticipantInfo {
// pub mut:
// sid string
// identity string
// name string
// state string
// tracks []TrackInfo
// metadata string
// joined_at i64
// permission ParticipantPermission
// is_publisher bool
// }
// pub struct TrackInfo {
// pub mut:
// sid string
// typ string @[json: 'type']
// source string
// name string
// mime_type string
// muted bool
// width u32
// height u32
// simulcast bool
// disable_dtx bool
// layers []VideoLayer
// }
// pub struct ParticipantPermission {
// pub mut:
// can_subscribe bool
// can_publish bool
// can_publish_data bool
// }
// pub struct VideoLayer {
// pub mut:
// quality string
// width u32
// height u32
// }
// // Helper method to make POST requests to LiveKit API
// fn (client Client) make_post_request(url string, body string) !http.Response {
// mut headers := http.new_header()
// headers.add_custom('Authorization', 'Bearer ${client.token}')!
// headers.add_custom('Content-Type', 'application/json')!
// req := http.Request{
// method: http.Method.post
// url: url
// data: body
// header: headers
// }
// return req.do()!
// }
// pub struct CreateRoomArgs {
// pub:
// name string
// empty_timeout u32
// max_participants u32
// metadata string
// }
// // RoomService API methods
// pub fn (client Client) create_room(args CreateRoomArgs) !Room {
// body := json.encode(args)
// url := '${client.host}/twirp/livekit.RoomService/CreateRoom'
// response := client.make_post_request(url, body)!
// return json.decode(Room, response.body)!
// }
// // pub fn (client Client) list_rooms(names []string) ![]Room {
// // body := json.encode({
// // 'names': names
// // })
// // url := '${client.host}/twirp/livekit.RoomService/ListRooms'
// // response := client.make_post_request(url, body)!
// // return json.decode([]Room, response.body)!
// // }
// pub fn (client Client) delete_room(room_name string) ! {
// body := json.encode({
// 'room': room_name
// })
// url := '${client.host}/twirp/livekit.RoomService/DeleteRoom'
// _ := client.make_post_request(url, body)!
// }
// pub fn (client Client) list_participants(room_name string) ![]ParticipantInfo {
// body := json.encode({
// 'room': room_name
// })
// url := '${client.host}/twirp/livekit.RoomService/ListParticipants'
// response := client.make_post_request(url, body)!
// return json.decode([]ParticipantInfo, response.body)!
// }
// pub fn (client Client) get_participant(room_name string, identity string) !ParticipantInfo {
// body := json.encode({
// 'room': room_name
// 'identity': identity
// })
// url := '${client.host}/twirp/livekit.RoomService/GetParticipant'
// response := client.make_post_request(url, body)!
// return json.decode(ParticipantInfo, response.body)!
// }
// pub fn (client Client) remove_participant(room_name string, identity string) ! {
// body := json.encode({
// 'room': room_name
// 'identity': identity
// })
// url := '${client.host}/twirp/livekit.RoomService/RemoveParticipant'
// _ := client.make_post_request(url, body)!
// }
// pub struct MutePublishedTrackArgs {
// pub:
// room_name string
// identity string
// track_sid string
// muted bool
// }
// pub fn (client Client) mute_published_track(args MutePublishedTrackArgs) ! {
// body := json.encode(args)
// url := '${client.host}/twirp/livekit.RoomService/MutePublishedTrack'
// _ := client.make_post_request(url, body)!
// }
// pub struct UpdateParticipantArgs {
// pub:
// room_name string @[json: 'room']
// identity string
// metadata string
// permission ParticipantPermission
// }
// pub fn (client Client) update_participant(args UpdateParticipantArgs) ! {
// body := json.encode(args)
// url := '${client.host}/twirp/livekit.RoomService/UpdateParticipant'
// _ := client.make_post_request(url, body)!
// }
// pub struct UpdateRoomMetadataArgs {
// pub:
// room_name string @[json: 'room']
// metadata string
// }
// pub fn (client Client) update_room_metadata(args UpdateRoomMetadataArgs) ! {
// body := json.encode(args)
// url := '${client.host}/twirp/livekit.RoomService/UpdateRoomMetadata'
// _ := client.make_post_request(url, body)!
// }
// pub struct SendDataArgs {
// pub:
// room_name string @[json: 'room']
// data []u8
// kind string
// destination_identities []string
// }
// pub fn (client Client) send_data(args SendDataArgs) ! {
// body := json.encode(args)
// url := '${client.host}/twirp/livekit.RoomService/SendData'
// _ := client.make_post_request(url, body)!
// }

View File

@@ -0,0 +1,52 @@
module livekit
// import jwt
import time
pub struct AccessToken {
pub mut:
api_key string
api_secret string
identity string
name string
ttl int
video_grant VideoGrant
}
pub struct VideoGrant {
pub mut:
room_create bool
room_admin bool
room_join bool
room_list bool
can_publish bool
can_subscribe bool
can_publish_data bool
room string
}
pub fn (mut c LivekitClient) new_access_token(identity string, name string, ttl int) !AccessToken {
return AccessToken{
api_key: c.api_key
api_secret: c.api_secret
identity: identity
name: name
ttl: ttl
}
}
pub fn (mut t AccessToken) add_video_grant(grant VideoGrant) {
t.video_grant = grant
}
pub fn (t AccessToken) to_jwt() !string {
mut claims := jwt.new_claims()
claims.iss = t.api_key
claims.sub = t.identity
claims.exp = time.now().unix_time() + t.ttl
claims.nbf = time.now().unix_time()
claims.iat = time.now().unix_time()
claims.name = t.name
claims.video = t.video_grant
return jwt.encode(claims, t.api_secret, .hs256)
}

View File

@@ -0,0 +1,77 @@
module livekit
import time
import rand
import crypto.hmac
import crypto.sha256
import encoding.base64
import json
// Struct representing grants
pub struct ClaimGrants {
pub mut:
video VideoGrant
iss string
exp i64
nbf int
sub string
name string
}
// // VideoGrant struct placeholder
// pub struct VideoGrant {
// pub mut:
// room string
// room_join bool @[json: 'roomJoin']
// room_list bool @[json: 'roomList']
// can_publish bool @[json: 'canPublish']
// can_publish_data bool @[json: 'canPublishData']
// can_subscribe bool @[json: 'canSubscribe']
// }
// SIPGrant struct placeholder
struct SIPGrant {}
// // AccessToken class
// pub struct AccessToken {
// mut:
// api_key string
// api_secret string
// grants ClaimGrants
// identity string
// ttl int
// }
// Method to add a video grant to the token
// pub fn (mut token AccessToken) add_video_grant(grant VideoGrant) {
// token.grants.video = grant
// }
// // Method to generate a JWT token
// pub fn (token AccessToken) to_jwt() !string {
// // Create JWT payload
// payload := json.encode(token.grants)
// println('payload: ${payload}')
// // Create JWT header
// header := '{"alg":"HS256","typ":"JWT"}'
// // Encode header and payload in base64
// header_encoded := base64.url_encode_str(header)
// payload_encoded := base64.url_encode_str(payload)
// // Create the unsigned token
// unsigned_token := '${header_encoded}.${payload_encoded}'
// // Create the HMAC-SHA256 signature
// signature := hmac.new(token.api_secret.bytes(), unsigned_token.bytes(), sha256.sum,
// sha256.block_size)
// // Encode the signature in base64
// signature_encoded := base64.url_encode(signature)
// // Create the final JWT
// jwt := '${unsigned_token}.${signature_encoded}'
// return jwt
// }