-
Notifications
You must be signed in to change notification settings - Fork 3
Tracking: Client WebRTC API #6
Comments
1 <=> 1 p2p fn main() {
one_to_one_p2p();
}
struct Turns {
turns: Vec<String>
}
struct UsersUpdate {
users: Vec<u64>
}
struct Offer {
user_id: u64,
sdp_offer: String,
}
struct Answer {
user_id: u64,
sdp_answer: String,
}
struct Candidate {
user_id: u64,
candidate: String,
}
struct WSMessage<T> {
payload: T
}
struct WS {
url: String
}
impl WS {
fn send<T>(&self, msg: WSMessage<T>) {}
fn recv<T>(&self, payload: T) -> WSMessage<T> {
WSMessage { payload }
}
}
fn one_to_one_p2p() {
// 1. User1 joins the room
let user1_ws = WS {
url: "wss://medea.com/ws/room_id?token=caller_token".to_owned()
};
// 2. User1 receives TURN servers list and just saves it
let turns1 = user1_ws.recv(Turns {
turns: Vec::new()
});
// 3. User2 joins the room
let user2_ws = WS {
url: "wss://medea.com/ws/room_id?token=responder_token".to_owned()
};
// 4. User2 receives TURN servers list and just saves it
let turns2 = user2_ws.recv(Turns {
turns: Vec::new()
});
// 5. User1 receives notification about second user entering the room
let users_update = user1_ws.recv(UsersUpdate {
users: vec![2]
});
// User 1 js
// var pc = new PeerConnection(saved_turns);
// var offer = pc.createOffer();
// pc.setLocalDescription(offer);
//6. User1 send offer to user 2
user1_ws.send(WSMessage {
payload: Offer {
user_id: 2,
sdp_offer: "user_1_sdp_offer".to_owned(),
}
});
//7. User2 receives offer from user1
let offer = user2_ws.recv(Offer {
user_id: 1,
sdp_offer: "user_1_sdp_offer".to_owned(),
});
// User2 js
// var pc = new PeerConnection(saved_turns);
// pc.setRemotDescription(offer);
// var answer = pc.createAnswer();
//8. User2 sends answer
user2_ws.send(WSMessage {
payload: Answer {
user_id: 1,
sdp_answer: "user_2_sdp_answer".to_owned(),
}
});
//9. User1 receives answer
let answer = user1_ws.recv(Answer {
user_id: 2,
sdp_answer: "user_2_sdp_answer".to_owned(),
});
//User1 js
// pc.setRemoteDescription(answer);
// Обмен ice candidates опущу - там уже ничего интересного нет.
// PeerConnection выплевывает кандидатов, мы их пересылаем.
} Тут все достаточно просто: Пользователь_1 получает уведомление когда в комнату заходит Пользователь_2 и инициирует связь. Уведомления о новых пользователях, пожалуй, стоит переделать на команду инициализации подключения. Сервер будет сам говорить клиентам кому и когда звонить. Таким образом мы избавимся от гонок, возникающих при перекрестных оферах. |
3 <=> 3 p2p fn main() {
three_to_three_p2p();
}
struct Turns {
turns: Vec<String>
}
struct OfferRequired {
user_id: u64
}
struct Offer {
user_id: u64,
sdp_offer: String,
}
struct Answer {
user_id: u64,
sdp_answer: String,
}
struct Candidate {
user_id: u64,
candidate: String,
}
struct WSMessage<T> {
payload: T
}
struct WS {
url: String
}
impl WS {
fn send<T>(&self, msg: WSMessage<T>) {}
fn recv<T>(&self, payload: T) -> WSMessage<T> {
WSMessage { payload }
}
}
// Упрощаем:
// 1. Убираем JS
// 2. Обьединяем ws connect и получение списка TURNS
// 3. Обьединяем отправку оффера с получением оффера, отправкой ответа, получением ответа
fn three_to_three_p2p() {
// 1. User1 joins the room, receives TURNS
let user1_ws = WS {
url: "wss://medea.com/ws/room_id?token=caller_token".to_owned()
};
// 2. User2 joins the room, receives TURNS
let user2_ws = WS {
url: "wss://medea.com/ws/room_id?token=responder_token".to_owned()
};
// 3. User1 receives notification about User2 entering the room
let users_update = user1_ws.recv(OfferRequired {
user_id: 2
});
// 4. User1 send offer to User2, receives answer, candidates
user1_ws.send(WSMessage {
payload: Offer {
user_id: 2,
sdp_offer: "user_1_sdp_offer".to_owned(),
}
});
//5. User3 joins the room, receives TURNS
let user3_ws = WS {
url: "wss://medea.com/ws/room_id?token=responder2_token".to_owned()
};
// 6. User1 receives notification about User3 entering the room
let users_update = user1_ws.recv(OfferRequired {
user_id: 3
});
// 7. User1 send offer to User3, receives answer, candidates
user1_ws.send(WSMessage {
payload: Offer {
user_id: 3,
sdp_offer: "user_1_sdp_offer".to_owned(),
}
});
// 8. User2 receives notification about User3 entering the room
let users_update = user2_ws.recv(OfferRequired {
user_id: 3
});
// 9. User2 send offer to User3, receives answer, candidates
user2_ws.send(WSMessage {
payload: Offer {
user_id: 3,
sdp_offer: "user_1_sdp_offer".to_owned(),
}
});
} Для п2п пока все складывается:
Так можно продолжать до бесконечности. Для каждого коннекта используется по одному А вот для трансляций через медиа сервер все не так однозначно. Есть три стула:
Говорят, что один RTCPeerConnection на все(+1 на сервере) хорошо экономит ресурсы, но сложнее в использовании. Учитывая, что на сервере планируется использование gst webrtcbin, все сводится к наличию необходимого функционала в webrtcbin. При этом, в p2p варианте всегда будет по
Но это чуть позже. Пока недостатки текущей реализации:
|
Про контроль направления: Тут есть 4 фактора:
Все коминации были протестированы(Chrome): Create offer:
Create answer:
Исходя из этого можно накидать как мы будем регулировать направления/ограничения. Например, 1 -> 1, и первым зашел подписчик:
|
Second draft. Добавилось:
Не хватает:
fn main() {
one_to_one_p2p();
one_to_one_p2p_single_publisher_publisher_enters_room_first();
one_to_one_p2p__single_publisher_subscriber_enters_room_first();
}
struct RTCOfferOptions {
ice_restart: bool,
add_track: Option<Track>,
offer_to_receive_audio: bool,
offer_to_receive_video: bool,
}
struct Track {
audio: bool,
video: bool,
}
// server => user
struct Turns {
turns: Vec<String>
}
// server => user
struct OfferRequest {
peer_id: u64,
rtc_offer_options: RTCOfferOptions,
}
// user => server => user
struct Offer {
peer_id: u64,
sdp_offer: String,
rtc_answer_options: Option<RTCOfferOptions>, //added on server, before passing to answerer
}
// user => server => user
struct Answer {
peer_id: u64,
sdp_answer: String,
}
//user => server => user
struct Candidate {
peer_id: u64,
candidate: String,
}
struct Leave {}
struct WS {
url: String
}
impl WS {
fn send<T>(&self, payload: T) {}
fn recv<T>(&self, payload: T) -> T {
payload
}
}
fn one_to_one_p2p() {
// 1. User1 joins the room, receives TURNS
let user1_ws = WS {
url: "wss://medea.com/ws/room_id?token=caller_token".to_owned()
};
// 2. User2 joins the room, receives TURNS
let user2_ws = WS {
url: "wss://medea.com/ws/room_id?token=responder_token".to_owned()
};
// 3. User1 receives OfferRequest
let user1_offer_request = user1_ws.recv(OfferRequest {
peer_id: 2,
rtc_offer_options: RTCOfferOptions {
ice_restart: false,
add_track: Some(Track {
audio: true,
video: true,
}),
offer_to_receive_audio: true,
offer_to_receive_video: true,
},
});
// 4. User1 JS
// check for endpoint_2 associated peerConnection => not found => create one
// add_track == true => so getUserMedia, addTrack()
// createOffer(offerToReceive: true) => sendrecv offer => setLocalDescription() => send to media server
// 5. User1 sends offer
user1_ws.send(Offer {
peer_id: 2,
sdp_offer: "sendrecv_offer".to_owned(),
rtc_answer_options: None,
});
// 6. User2 receives offer
let user2_received_offer = user2_ws.recv(Offer {
peer_id: 1,
sdp_offer: "sendrecv_offer".to_owned(),
rtc_answer_options: Some(RTCOfferOptions {
ice_restart: false,
add_track: Some(Track {
audio: true,
video: true,
}),
offer_to_receive_audio: true,
offer_to_receive_video: true,
}),
});
// 7. User2 JS
// check for endpoint_1 associated peerConnection => not found => create one
// add_track == true => so getUserMedia(), addTrack()
// setRemoteDescription(offer)
// peerConection.createAnswer(offerToReceive: true) => sendrecv answer => send to media server
// 8. User2 sends answer
user2_ws.send(Answer { peer_id: 1, sdp_answer: "sendrecv_answer".to_owned() })
// User1 receives answer, users exchange candidates
}
fn one_to_one_p2p_single_publisher_publisher_enters_room_first() {
// 1. User1 joins the room, receives TURNS
let user1_ws = WS {
url: "wss://medea.com/ws/room_id?token=caller_token".to_owned()
};
// 2. User2 joins the room, receives TURNS
let user2_ws = WS {
url: "wss://medea.com/ws/room_id?token=responder_token".to_owned()
};
// 3. User1 receives OfferRequest
let user1_offer_request = user1_ws.recv(OfferRequest {
peer_id: 2,
rtc_offer_options: RTCOfferOptions {
ice_restart: false,
add_track: Some(Track {
audio: true,
video: true,
}),
offer_to_receive_audio: false,
offer_to_receive_video: false,
},
});
// 4. User1 JS
// check for endpoint_2 associated peerConnection => not found => create one
// add_track == true => so getUserMedia, addTrack()
// createOffer(offerToReceive: false) => sendrecv offer => setLocalDescription() => send to media server
// 5. User1 sends offer
user1_ws.send(Offer {
peer_id: 2,
sdp_offer: "sendonly_offer".to_owned(),
rtc_answer_options: None,
});
// 6. User2 receives offer
let user2_received_offer = user2_ws.recv(Offer {
peer_id: 1,
sdp_offer: "sendonly_offer".to_owned(),
rtc_answer_options: Some(RTCOfferOptions {
ice_restart: false,
add_track: None,
offer_to_receive_audio: true,
offer_to_receive_video: true,
}),
});
// 7. User2 JS
// check for endpoint_1 associated peerConnection => not found => create one
// add_track == false
// setRemoteDescription(offer)
// peerConection.createAnswer(offerToReceive: true) => recvonly answer => send to media server
// 8. User2 sends answer
user2_ws.send(Answer { peer_id: 1, sdp_answer: "recvonly_answer".to_owned() })
// User1 receives answer, users exchange candidates
}
fn one_to_one_p2p__single_publisher_subscriber_enters_room_first() {
// 1. User1 joins the room, receives TURNS
let user1_ws = WS {
url: "wss://medea.com/ws/room_id?token=responder_token".to_owned()
};
// 2. User2 joins the room, receives TURNS
let user2_ws = WS {
url: "wss://medea.com/ws/room_id?token=caller_token".to_owned()
};
// 3. User1 receives OfferRequest
let user1_offer_request = user1_ws.recv(OfferRequest {
peer_id: 2,
rtc_offer_options: RTCOfferOptions {
ice_restart: false,
add_track: None,
offer_to_receive_audio: true,
offer_to_receive_video: true,
},
});
// 4. User1 JS
// check for endpoint_2 associated peerConnection => not found => create one
// add_track == false
// createOffer(offerToReceive: true) => recvonly offer => setLocalDescription() => send to media server
// 5. User1 sends offer
user1_ws.send(Offer {
peer_id: 2,
sdp_offer: "recvonly_offer".to_owned(),
rtc_answer_options: None,
});
// 6. User2 receives offer
let user2_received_offer = user2_ws.recv(Offer {
peer_id: 1,
sdp_offer: "recvonly_offer".to_owned(),
rtc_answer_options: Some(RTCOfferOptions {
ice_restart: false,
add_track: Some(Track{
audio: true,
video: true
}),
offer_to_receive_audio: false,
offer_to_receive_video: false,
}),
});
// 7. User2 JS
// check for endpoint_1 associated peerConnection => not found => create one
// add_track == true => getUserMedia(), addTrack()
// setRemoteDescription(offer)
// peerConection.createAnswer(offerToReceive: false) => sendonly answer => send to media server
// 8. User2 sends answer
user2_ws.send(Answer { peer_id: 1, sdp_answer: "sendonly".to_owned() })
// User1 receives answer, users exchange candidates
} |
Third draft Добавилось:
Проблемы:
По третьему пункту: думаю, надо давать клиенту данные с Control API. Например, по этому примеру: pipeline: hashmap!{
"publisher" => Member {
on_join: Some("grpc://127.0.0.1:9090"),
on_leave: Some("grpc://127.0.0.1:9090"),
pipeline: hashmap!{
"webrtc" => WebRtcPublishEndpoint {
p2p: false,
on_start: Some("grpc://127.0.0.1:9090"),
on_stop: Some("grpc://127.0.0.1:9090"),
}
},
},
"player-1" => Member {
on_join: Some("grpc://127.0.0.1:9090"),
on_leave: Some("grpc://127.0.0.1:9090"),
pipeline: hashmap!{
"webrtc" => WebRtcPlayEndpoint {
src: "member://publisher/webrtc",
on_start: Some("grpc://127.0.0.1:9090"),
on_stop: Some("grpc://127.0.0.1:9090"),
},
},
},
"player-2" => Member {
on_join: Some("grpc://127.0.0.1:9090"),
on_leave: Some("grpc://127.0.0.1:9090"),
pipeline: hashmap!{
"hls" => HlsPlayEndpoint {
src: "member://publisher/webrtc",
on_start: Some("grpc://127.0.0.1:9090"),
on_stop: Some("grpc://127.0.0.1:9090"),
},
},
},
} Пользователям "player-1" и "player-2" вместе с fn main() {
one_to_one_p2p();
one_to_one_p2p_single_publisher_publisher_enters_room_first();
one_to_one_p2p_single_publisher_subscriber_enters_room_first();
two_pub_one_sub_with_two_peerconnections_sfu();
two_pub_one_sub_with_one_peerconnection_sfu_and_unsubscribe();
}
struct RTCOfferOptions {
ice_restart: bool,
add_track: Option<Track>,
offer_to_receive_audio: bool,
offer_to_receive_video: bool,
}
struct Track {
audio: bool,
video: bool,
}
// server => user
struct Turns {
turns: Vec<String>
}
struct Stream {
peer_id: u64,
stream_id: u64,
}
// server => user
struct OfferRequest {
stream: Stream,
// stream_id: u64,
rtc_offer_options: RTCOfferOptions,
}
// user => server => user
struct Offer {
stream: Stream,
// stream_id: u64,
sdp_offer: String,
rtc_answer_options: Option<RTCOfferOptions>, //added on server, before passing to answerer
}
// user => server => user
struct Answer {
stream: Stream,
// stream_id: u64,
sdp_answer: String,
}
//user => server => user
struct Candidate {
stream: Stream,
stream_id: u64,
candidate: String,
}
// user => server, server => user
struct Unpublish {
stream: Stream,
}
// user => server, server => user
struct Unsubscribe {
stream: Stream,
}
struct Leave {}
// multiple recv streams in single peer
// multiple send stremas in single peer
struct WS {
url: String
}
impl WS {
fn send<T>(&self, payload: T) {}
fn recv<T>(&self, payload: T) -> T {
payload
}
}
fn one_to_one_p2p() {
// 1. User1 joins the room, receives TURNS
let user1_ws = WS {
url: "wss://medea.com/ws/room_id?token=caller_token".to_owned()
};
// 2. User2 joins the room, receives TURNS
let user2_ws = WS {
url: "wss://medea.com/ws/room_id?token=responder_token".to_owned()
};
// 3. User1 receives OfferRequest
let user1_offer_request = user1_ws.recv(OfferRequest {
stream: Stream {
peer_id: 2,
stream_id: 0,
},
rtc_offer_options: RTCOfferOptions {
ice_restart: false,
add_track: Some(Track {
audio: true,
video: true,
}),
offer_to_receive_audio: true,
offer_to_receive_video: true,
},
});
// 4. User1 JS
// check for endpoint_2 associated peerConnection => not found => create one
// add_track == true => so getUserMedia, addTrack()
// createOffer(offerToReceive: true) => sendrecv offer => setLocalDescription() => send to media server
// 5. User1 sends offer
user1_ws.send(Offer {
stream: Stream {
peer_id: 2,
stream_id: 0,
},
sdp_offer: "sendrecv_offer".to_owned(),
rtc_answer_options: None,
});
// 6. User2 receives offer
let user2_received_offer = user2_ws.recv(Offer {
stream: Stream {
peer_id: 1,
stream_id: 0,
},
sdp_offer: "sendrecv_offer".to_owned(),
rtc_answer_options: Some(RTCOfferOptions {
ice_restart: false,
add_track: Some(Track {
audio: true,
video: true,
}),
offer_to_receive_audio: true,
offer_to_receive_video: true,
}),
});
// 7. User2 JS
// check for endpoint_1 associated peerConnection => not found => create one
// add_track == true => so getUserMedia(), addTrack()
// setRemoteDescription(offer)
// peerConection.createAnswer(offerToReceive: true) => sendrecv answer => send to media server
// 8. User2 sends answer
user2_ws.send(Answer {
stream: Stream {
peer_id: 1,
stream_id: 0,
},
sdp_answer: "sendrecv_answer".to_owned(),
})
// User1 receives answer, users exchange candidates
}
fn one_to_one_p2p_single_publisher_publisher_enters_room_first() {
// 1. User1 joins the room, receives TURNS
let user1_ws = WS {
url: "wss://medea.com/ws/room_id?token=caller_token".to_owned()
};
// 2. User2 joins the room, receives TURNS
let user2_ws = WS {
url: "wss://medea.com/ws/room_id?token=responder_token".to_owned()
};
// 3. User1 receives OfferRequest
let user1_offer_request = user1_ws.recv(OfferRequest {
stream: Stream {
peer_id: 2,
stream_id: 0,
},
rtc_offer_options: RTCOfferOptions {
ice_restart: false,
add_track: Some(Track {
audio: true,
video: true,
}),
offer_to_receive_audio: false,
offer_to_receive_video: false,
},
});
// 4. User1 JS
// check for endpoint_2 associated peerConnection => not found => create one
// add_track == true => so getUserMedia, addTrack()
// createOffer(offerToReceive: false) => sendrecv offer => setLocalDescription() => send to media server
// 5. User1 sends offer
user1_ws.send(Offer {
stream: Stream {
peer_id: 2,
stream_id: 0,
},
sdp_offer: "sendonly_offer".to_owned(),
rtc_answer_options: None,
});
// 6. User2 receives offer
let user2_received_offer = user2_ws.recv(Offer {
stream: Stream {
peer_id: 1,
stream_id: 0,
},
sdp_offer: "sendonly_offer".to_owned(),
rtc_answer_options: Some(RTCOfferOptions {
ice_restart: false,
add_track: None,
offer_to_receive_audio: true,
offer_to_receive_video: true,
}),
});
// 7. User2 JS
// check for endpoint_1 associated peerConnection => not found => create one
// add_track == false
// setRemoteDescription(offer)
// peerConection.createAnswer(offerToReceive: true) => recvonly answer => send to media server
// 8. User2 sends answer
user2_ws.send(Answer {
stream: Stream {
peer_id: 1,
stream_id: 0,
},
sdp_answer: "recvonly_answer".to_owned(),
})
// User1 receives answer, users exchange candidates
}
fn one_to_one_p2p_single_publisher_subscriber_enters_room_first() {
// 1. User1 joins the room, receives TURNS
let user1_ws = WS {
url: "wss://medea.com/ws/room_id?token=responder_token".to_owned()
};
// 2. User2 joins the room, receives TURNS
let user2_ws = WS {
url: "wss://medea.com/ws/room_id?token=caller_token".to_owned()
};
// 3. User1 receives OfferRequest
let user1_offer_request = user1_ws.recv(OfferRequest {
stream: Stream {
peer_id: 2,
stream_id: 0,
},
rtc_offer_options: RTCOfferOptions {
ice_restart: false,
add_track: None,
offer_to_receive_audio: true,
offer_to_receive_video: true,
},
});
// 4. User1 JS
// check for endpoint_2 associated peerConnection => not found => create one
// add_track == false
// createOffer(offerToReceive: true) => recvonly offer => setLocalDescription() => send to media server
// 5. User1 sends offer
user1_ws.send(Offer {
stream: Stream {
peer_id: 2,
stream_id: 0,
},
sdp_offer: "recvonly_offer".to_owned(),
rtc_answer_options: None,
});
// 6. User2 receives offer
let user2_received_offer = user2_ws.recv(Offer {
stream: Stream {
peer_id: 1,
stream_id: 0,
},
sdp_offer: "recvonly_offer".to_owned(),
rtc_answer_options: Some(RTCOfferOptions {
ice_restart: false,
add_track: Some(Track {
audio: true,
video: true,
}),
offer_to_receive_audio: false,
offer_to_receive_video: false,
}),
});
// 7. User2 JS
// check for endpoint_1 associated peerConnection => not found => create one
// add_track == true => getUserMedia(), addTrack()
// setRemoteDescription(offer)
// peerConection.createAnswer(offerToReceive: false) => sendonly answer => send to media server
// 8. User2 sends answer
user2_ws.send(Answer {
stream: Stream {
peer_id: 1,
stream_id: 0,
},
sdp_answer: "sendonly".to_owned(),
})
// User1 receives answer, users exchange candidates
}
// two_pub_one_sub_with_two_peerconnections_sfu
//
// .-------SFU--------. .--------sub--------.
// pub1 peer_con_1(1)--->-o(1)->--(1-3)---->-o(3)->-o(3)->peer_con_1(3) :
// : : : :
// pub2 peer_con_1(2)--->-o(2)->--(2-4)---->-o(4)->-o(4)->peer_con_2(4) :
// '------------------' '-------------------'
fn two_pub_one_sub_with_two_peerconnections_sfu() {
// 1. Pub 1 publishes on server.
// 1.1. pub1 joins the room, receives TURNS
let pub1_ws = WS {
url: "wss://medea.com/ws/room_id?token=pub1_token".to_owned()
};
// 1.2. pub1 receives OfferRequest
let pub1_offer_request = pub1_ws.recv(OfferRequest {
stream: Stream {
peer_id: 1,
stream_id: 0,
},
rtc_offer_options: RTCOfferOptions {
ice_restart: false,
add_track: Some(Track {
audio: true,
video: true,
}),
offer_to_receive_audio: false,
offer_to_receive_video: false,
},
});
// 1.3. pub1 sends offer
pub1_ws.send(Offer {
stream: Stream {
peer_id: 1,
stream_id: 0,
},
sdp_offer: "pub1_sendonly_offer".to_owned(),
rtc_answer_options: None,
});
// 1.4. server creates recvonly answer
let pub1_answer = pub1_ws.recv(Answer {
stream: Stream {
peer_id: 1,
stream_id: 0,
},
sdp_answer: "pub1_recvonly_answer".to_owned(),
});
// 1.5. ice, connection pub1->server created
// 2. Pub 2 publishes on server
// 2.1. pub2 joins the room, receives TURNS
let pub2_ws = WS {
url: "wss://medea.com/ws/room_id?token=pub2_token".to_owned()
};
// 2.2. pub2 receives OfferRequest
let pub2_offer_request = pub2_ws.recv(OfferRequest {
stream: Stream {
peer_id: 2,
stream_id: 0,
},
rtc_offer_options: RTCOfferOptions {
ice_restart: false,
add_track: Some(Track {
audio: true,
video: true,
}),
offer_to_receive_audio: false,
offer_to_receive_video: false,
},
});
// 2.3. pub2 sends offer
pub2_ws.send(Offer {
stream: Stream {
peer_id: 2,
stream_id: 0,
},
sdp_offer: "pub2_sendonly_offer".to_owned(),
rtc_answer_options: None,
});
// 2.4. server creates recvonly answer
let pub2_answer = pub2_ws.recv(Answer {
stream: Stream {
peer_id: 2,
stream_id: 0,
},
sdp_answer: "pub2_recvonly_answer".to_owned(),
});
// 2.5. ice, connection pub2->server created
// 3. sub subscribes to pub1 video
// 3.1. sub connects, sets TURNS
let sub_ws = WS {
url: "wss://medea.com/ws/room_id?token=sub_token".to_owned()
};
// 3.2. sub1 receives OfferRequest
let sub_offer_request = sub_ws.recv(OfferRequest {
stream: Stream {
peer_id: 3,
stream_id: 0,
},
rtc_offer_options: RTCOfferOptions {
ice_restart: false,
add_track: None,
offer_to_receive_audio: true,
offer_to_receive_video: true,
},
});
// 3.3. sub send offer
sub_ws.send(Offer {
stream: Stream {
peer_id: 3,
stream_id: 0,
},
sdp_offer: "sub_recvonly_offer".to_owned(),
rtc_answer_options: None,
});
// 3.4. server creates answer
let sub_answer = sub_ws.recv(Answer {
stream: Stream {
peer_id: 3,
stream_id: 0,
},
sdp_answer: "sub_sendonly_answer".to_owned(),
});
// 3.5. ice, connection sub<-server created
}
// two_pub_one_sub_with_one_peerconnection_sfu
//
// .---------SFU------.
// pub1 peer_con_1(1)--->-o(1)-->---o(1-3)---: .-------sub---------.
// : : o(3)-->-o(3)->peer_con_1(3) :
// pub2 peer_con_1(2)--->-o(2)-->---o(2-3)---: '-------------------'
// '------------------'
fn two_pub_one_sub_with_one_peerconnection_sfu_and_unsubscribe() {
// 1. Pub 1 publishes on server.
// 1.1. pub1 joins the room, receives TURNS
let pub1_ws = WS {
url: "wss://medea.com/ws/room_id?token=pub1_token".to_owned()
};
// 1.2. pub1 receives OfferRequest
let pub1_offer_request = pub1_ws.recv(OfferRequest {
stream: Stream {
peer_id: 1,
stream_id: 0,
},
rtc_offer_options: RTCOfferOptions {
ice_restart: false,
add_track: Some(Track {
audio: true,
video: true,
}),
offer_to_receive_audio: false,
offer_to_receive_video: false,
},
});
// 1.3. pub1 sends offer
pub1_ws.send(Offer {
stream: Stream {
peer_id: 1,
stream_id: 0,
},
sdp_offer: "pub1_sendonly_offer".to_owned(),
rtc_answer_options: None,
});
// 1.4. server creates recvonly answer
let pub1_answer = pub1_ws.recv(Answer {
stream: Stream {
peer_id: 1,
stream_id: 0,
},
sdp_answer: "pub1_recvonly_answer".to_owned(),
});
// 1.5. ice, connection pub->server created
// 2. sub subscribes to first publisher
// 2.1. sub opens ws, receives turns
let sub_ws = WS {
url: "wss://medea.com/ws/room_id?token=sub_token".to_owned()
};
// 2.2. sub receives OfferRequest to subscribe to first pub
let sub_offer_request = sub_ws.recv(OfferRequest {
stream: Stream {
peer_id: 3,
stream_id: 0,
},
rtc_offer_options: RTCOfferOptions {
ice_restart: false,
add_track: None,
offer_to_receive_audio: true,
offer_to_receive_video: true,
},
});
// 2.3. sub send offer
sub_ws.send(Offer {
stream: Stream {
peer_id: 3,
stream_id: 0,
},
sdp_offer: "sub_recvonly_1_offer".to_owned(),
rtc_answer_options: None,
});
// 2.4. server returns sendonly answer
let sub_answer_1 = sub_ws.recv(Answer {
stream: Stream {
peer_id: 3,
stream_id: 0,
},
sdp_answer: "sub1_sendonly_answer".to_owned(),
});
// 2.5. ice, connection sub<-server(pub1) created
// 3. Pub 2 publishes on server.
// 3.1. pub2 joins the room, receives TURNS
let pub2_ws = WS {
url: "wss://medea.com/ws/room_id?token=pub2_token".to_owned()
};
// 3.2. pub2 receives OfferRequest
let pub2_offer_request = pub2_ws.recv(OfferRequest {
stream: Stream {
peer_id: 2,
stream_id: 1,
},
rtc_offer_options: RTCOfferOptions {
ice_restart: false,
add_track: Some(Track {
audio: true,
video: true,
}),
offer_to_receive_audio: false,
offer_to_receive_video: false,
},
});
// 3.3. pub2 sends offer
pub2_ws.send(Offer {
stream: Stream {
peer_id: 2,
stream_id: 1,
},
sdp_offer: "pub2_sendonly_offer".to_owned(),
rtc_answer_options: None,
});
// 3.4. server creates recvonly answer
let pub2_answer = pub2_ws.recv(Answer {
stream: Stream {
peer_id: 2,
stream_id: 1,
},
sdp_answer: "pub2_recvonly_answer".to_owned(),
});
// 3.5. ice, connection pub2->server created
// 4. sub subscribes to first publisher
let sub_ws = WS {
url: "wss://medea.com/ws/room_id?token=sub_token".to_owned()
};
// 4.2. sub receives OfferRequest to subscribe to first pub
let sub_offer_request = sub_ws.recv(OfferRequest {
stream: Stream {
peer_id: 3,
stream_id: 1,
},
rtc_offer_options: RTCOfferOptions {
ice_restart: false,
add_track: None,
offer_to_receive_audio: true,
offer_to_receive_video: true,
},
});
// 4.3. sub send offer
sub_ws.send(Offer {
stream: Stream {
peer_id: 3,
stream_id: 1,
},
sdp_offer: "sub_recvonly_1_offer".to_owned(),
rtc_answer_options: None,
});
// 4.4. server returns sendonly answer
let server_answer_1 = sub_ws.recv(Answer {
stream: Stream {
peer_id: 3,
stream_id: 1,
},
sdp_answer: "sub1_sendonly_answer".to_owned(),
});
// 4.5. ice, connection sub<-server(pub2) created
// role direction peer stream
// pub1 => 1 0
// sub <= 3 0
// pub2 => 2 1
// sub <= 3 1
// 5. Sub unsubscribes from pub1
// 5.1. Sub initializes unsubscribe
sub_ws.send(Unsubscribe {
stream: Stream {
peer_id: 3,
stream_id: 0,
}
});
// 5.2. Server does any stuff that it needs to do(i.e invokes some callback) and returns same request
let unsubscribe_request = sub_ws.recv(Unsubscribe {
stream: Stream {
peer_id: 3,
stream_id: 0,
}
});
}
// two_pub_two_sub_with_one_peerconnection_sfu
//
// .----------SFU---------.
// : .->--o(1-3)---: .-------sub1-------.
// pub1 peer_con_1(1)--->-o(1)-->--: : o(3)-->--o(3)->peer_con1(3):
// : .->-+-->-o(2-3)---: '------------------'
// : : : :
// : . '->--o(1-4)---: .-------sub2-------.
// pub2 peer_con_1(2)--->-o(2)-: : o(4)-->---o(4)->peer_con1(4) :
// : '->--->--o(2-4)---: '------------------'
// '----------------------'
//
// two_pub_two_sub_with_two_peerconnections_sfu
//
// .----------SFU---------. .-----sub1-------.
// : .------o(1-3)-->--o(3)->peer_con_1 :
// : : : | :
// pub1 peer_con_1(1)--->-o(1)---->--->---: .---o(2-4)-->--o(4)->peer_con_2 :
// : .----+--' : '----------------'
// : : : : .----------------.
// : : '------o(1-5)-->--o(5)->peer_con_1 :
// pub2 peer_con_1(2)--->-o(2)--->---: : | :
// : '-----------o(2-6)-->--o(6)->peer_con_2 :
// '----------------------' '----------------' |
4th draft // Used primitives
//
// .-------------------------Member---------------------------.
// : .-----------Peer----------. .------------Peer---------. :
// : : .--Track--. .--Track--. : : .--Track--. .--Track--. : :
// : : : video : : audio : : : : video : : audio : : :
// : : : : : : : : : : : : : :
// : : '----o----' '---------' : : '---------' '----o----' : :
// : '------|------------------' '------------------|------' :
// '--------V----------------------------------------Λ--------'
// : :
// : Λ
// :------->------>-------. '---.
// : : :
// V : Λ
// : : :
// .--------V--------. .--------V--------. .--------Λ--------.
// : .------|------. : : .------|------. : : .------|------. :
// : : .----o----. : : : : .----o----. : : : : .----o----. : :
// : : : video : : : : : : video : : : : : : audio : : :
// : : : : : : : : : : : : : : : : : :
// : : '--Track--' : : : : '--Track--' : : : : '--Track--' : :
// : '-----Peer----' : : '-----Peer----' : : '-----Peer----' :
// '------Member-----' '------Member-----' '------Member-----'
// structs
struct Member {
peer_id: u64,
member_id: String,
}
struct Peer {
peer_id: u64,
// p2p peer send tracks, offers, answer, candidates MUST have remote peer reference
// non p2p peer send tracks MAY have remote peers ref
// non p2p peer offers, answers, candidates MUST NOT have remote peer ref
p2p: bool,
tracks: Vec<Track>,
}
struct Track {
id: u64,
media_type: TrackMediaType,
direction: TrackDirection,
}
enum TrackDirection {
// vec of receiver peers
// p2p peer send tracks MUST have only one receiver
// non p2p peer send tracks MAY have receivers(0-N)
Send(Vec<u64>),
// sender peer id
Recv(u64),
}
enum TrackMediaType {
Audio(AudioSettings),
Video(VideoSettings),
}
struct AudioSettings {}
struct VideoSettings {}
struct RemotePeer {
// None if remote peer is SFU without receivers
peer_id: Option<u64>,
member_id: String,
can_rx: Option<RemotePeerTrackType>,
can_tx: Option<RemotePeerTrackType>,
}
enum RemotePeerTrackType {
Audio,
Video,
AudioVideo,
}
struct Error {}
// messages wrapper, wont be used in examples
// protocol demands every message(except Candidate) to be answered. Answer can have no payload.
struct WsMessage<T> {
id: u64,
payload: Option<Result<T, Error>>,
}
// messages
struct Turns {
turns: Vec<String>
}
struct AddPeer {
peer: Peer,
// if None, client must create peer and send offer
// if Some, client must create peer, set offer and send answer
sdp_offer: Option<String>,
}
struct RemovePeer {
peer_id: u64,
}
struct UpdateTracks {
peer_id: u64,
tracks: Vec<Track>,
}
struct RequestTracks {
peer_id: u64,
remote_peer_id: u64,
rx: Option<RemotePeerTrackType>,
tx: Option<RemotePeerTrackType>,
}
struct RemoveTracks {
peer_id: u64,
tracks: Vec<u64>,
}
struct Offer {
peer_id: u64,
// None if peer is not p2p
remote_peer_id: Option<u64>,
sdp_offer: String,
}
struct Answer {
peer_id: u64,
// None if peer is not p2p
remote_peer_id: Option<u64>,
sdp_answer: String,
}
struct Candidate {
peer_id: u64,
// None if peer is not p2p
remote_peer_id: Option<u64>,
candidate: String,
}
struct RemotePeers {
peers: Vec<RemotePeer>
}
struct Ping {}
struct Leave {}
// user requests peers member info
struct GetMembers {
peers: Vec<u64>,
}
// servers returns peers member info
struct Members {
members: Vec<Member>
}
struct WS {
url: String
}
impl WS {
fn send<T>(&self, payload: T) {}
fn recv<T>(&self, payload: T) -> T {
payload
}
}
// one_to_one_p2p
//
// .-----user1------. .->-->-->--. .-------user2-------.
// : pc_id = 2 o(1)=: :=o(2) pc_id = 1 :
// '----------------' '-<--<--<--' '-------------------'
//
fn one_to_one_p2p_unpublish_publish() {
// server => user1
AddPeer {
peer: Peer {
peer_id: 1,
p2p: true,
tracks: vec![
Track {
id: 1,
media_type: TrackMediaType::Audio(AudioSettings {}),
direction: TrackDirection::Send(vec![2]),
},
Track {
id: 2,
media_type: TrackMediaType::Video(VideoSettings {}),
direction: TrackDirection::Send(vec![2]),
},
Track {
id: 3,
media_type: TrackMediaType::Audio(AudioSettings {}),
direction: TrackDirection::Recv(2),
},
Track {
id: 4,
media_type: TrackMediaType::Video(VideoSettings {}),
direction: TrackDirection::Recv(2),
}],
},
sdp_offer: None,
};
// user1 => server
Offer {
peer_id: 1,
remote_peer_id: Some(2),
sdp_offer: "user1_sendrecv_offer".to_owned(),
};
// server => user2
AddPeer {
peer: Peer {
peer_id: 2,
p2p: true,
tracks: vec![
Track {
id: 1,
media_type: TrackMediaType::Audio(AudioSettings {}),
direction: TrackDirection::Recv(1),
},
Track {
id: 2,
media_type: TrackMediaType::Audio(AudioSettings {}),
direction: TrackDirection::Recv(1),
},
Track {
id: 3,
media_type: TrackMediaType::Audio(AudioSettings {}),
direction: TrackDirection::Send(vec![1]),
},
Track {
id: 4,
media_type: TrackMediaType::Video(VideoSettings {}),
direction: TrackDirection::Send(vec![1]),
}],
},
sdp_offer: Some("user1_sendrecv_offer".to_owned()),
};
// user2 => server => user1
Answer {
peer_id: 2,
remote_peer_id: None,
sdp_answer: "user2_sendrecv_answer".to_owned(),
};
// .----user1----. .->-->-->--. .----user2----.
// : o(1)=: :=o(2) :
// '-------------' '-<--<--<--' '-------------'
// unpublish
// user1 => server
RemoveTracks { peer_id: 1, tracks: vec![1, 2] };
// server => user1
RemoveTracks { peer_id: 1, tracks: vec![1, 2] };
// server => user2
RemoveTracks { peer_id: 2, tracks: vec![1, 2] };
// renegotiation
// .----user1----. .----user2----.
// : o(1)-<--<-o(2) :
// '-------------' '-------------'
// server => user1
RemotePeers {
peers: vec![RemotePeer {
peer_id: Some(2),
member_id: "user_2".to_owned(),
can_rx: None,
can_tx: Some(RemotePeerTrackType::AudioVideo),
}]
};
// server => user2
RemotePeers {
peers: vec![RemotePeer {
peer_id: Some(1),
member_id: "user_2".to_owned(),
can_rx: Some(RemotePeerTrackType::AudioVideo),
can_tx: None,
}]
};
// user1 => server
RequestTracks {
peer_id: 1,
remote_peer_id: 2,
rx: None,
tx: Some(RemotePeerTrackType::AudioVideo),
};
// server => user1
UpdateTracks {
peer_id: 1,
tracks: vec![
Track {
id: 1,
media_type: TrackMediaType::Audio(AudioSettings {}),
direction: TrackDirection::Send(vec![2]),
},
Track {
id: 2,
media_type: TrackMediaType::Video(VideoSettings {}),
direction: TrackDirection::Send(vec![2]),
},
],
};
// server => user2
UpdateTracks {
peer_id: 2,
tracks: vec![
Track {
id: 1,
media_type: TrackMediaType::Audio(AudioSettings {}),
direction: TrackDirection::Recv(1),
},
Track {
id: 2,
media_type: TrackMediaType::Audio(AudioSettings {}),
direction: TrackDirection::Recv(1),
},
],
};
// renegotiation
// .----user1----. .->-->-->--. .----user2----.
// : o(1)=: :=o(2) :
// '-------------' '-<--<--<--' '-------------'
}
//-------------------------------------------------------------------------------------------------
// one_to_one_sfu_two_peerconnections
//
// .------user1------. .------SFU------. .------user2-----.
// : pc_id = 1 o--->---o-->---->---->--o--->---o pc_id = 2 :
// : : : : : :
// : pc_id = 4 o---<---o--<----<-----<-o---<---o pc_id = 3 :
// '-----------------' '---------------' '----------------'
//
fn one_to_one_sfu_two_peerconnections() {
// server => user1
AddPeer {
peer: Peer {
peer_id: 1,
p2p: false,
tracks: vec![
Track {
id: 1,
media_type: TrackMediaType::Audio(AudioSettings {}),
direction: TrackDirection::Send(vec![]),
},
Track {
id: 2,
media_type: TrackMediaType::Video(VideoSettings {}),
direction: TrackDirection::Send(vec![]),
}
],
},
sdp_offer: Some("server_user1_recvonly_offer".to_owned()),
};
// user1 => server
Answer {
peer_id: 1,
remote_peer_id: None,
sdp_answer: "user1_sendonly_answer".to_owned(),
};
// .------user1------. .-------SFU-------.
// : pc_id = 1 o--->---o :
// : : : :
// : : : :
// '-----------------' '-----------------'
// server => user2
AddPeer {
peer: Peer {
peer_id: 2,
p2p: false,
tracks: vec![
Track {
id: 1,
media_type: TrackMediaType::Audio(AudioSettings {}),
direction: TrackDirection::Recv(1),
},
Track {
id: 2,
media_type: TrackMediaType::Video(VideoSettings {}),
direction: TrackDirection::Recv(1),
},
],
},
sdp_offer: Some("server_user2_sendonly_offer".to_owned()),
};
// user2 => server
Answer {
peer_id: 2,
remote_peer_id: None,
sdp_answer: "user2_recvonly_answer".to_owned(),
};
// server => user1
// just telling user1 that he has subscriber now
UpdateTracks {
peer_id: 1,
tracks: vec![
Track {
id: 1,
media_type: TrackMediaType::Audio(AudioSettings {}),
direction: TrackDirection::Send(vec![2]),
},
Track {
id: 2,
media_type: TrackMediaType::Video(VideoSettings {}),
direction: TrackDirection::Send(vec![2]),
}
],
};
// .------user1------. .-------SFU-----. .------user2-----.
// : pc_id = 1 o--->---o-->---->---->--o--->---o pc_id = 2 :
// : : : : : :
// : : : : : :
// '-----------------' '---------------' '----------------'
// server => user2
AddPeer {
peer: Peer {
peer_id: 3,
p2p: false,
tracks: vec![
Track {
id: 3,
media_type: TrackMediaType::Audio(AudioSettings {}),
direction: TrackDirection::Send(vec![]),
},
Track {
id: 4,
media_type: TrackMediaType::Video(VideoSettings {}),
direction: TrackDirection::Send(vec![]),
}
],
},
sdp_offer: Some("server_user2_recvonly_offer".to_owned()),
};
// user2 => server
Answer {
peer_id: 3,
remote_peer_id: None,
sdp_answer: "user2_sendonly_answer".to_owned(),
};
// .------user1------. .------SFU------. .------user2-----.
// : pc_id = 1 o--->---o-->---->---->--o--->---o pc_id = 2 :
// : : : : : :
// : : : o---<---o pc_id = 3 :
// '-----------------' '---------------' '----------------'
// server => user1
AddPeer {
peer: Peer {
peer_id: 4,
p2p: false,
tracks: vec![
Track {
id: 3,
media_type: TrackMediaType::Audio(AudioSettings {}),
direction: TrackDirection::Recv(3),
},
Track {
id: 4,
media_type: TrackMediaType::Video(VideoSettings {}),
direction: TrackDirection::Recv(3),
},
],
},
sdp_offer: Some("server_user1_sendonly_offer".to_owned()),
};
// user2 => server
Answer {
peer_id: 4,
remote_peer_id: None,
sdp_answer: "user1_recvonly_answer".to_owned(),
};
// server => user1
// just telling user2 that he has subscriber now
UpdateTracks {
peer_id: 3,
tracks: vec![
Track {
id: 3,
media_type: TrackMediaType::Audio(AudioSettings {}),
direction: TrackDirection::Send(vec![4]),
},
Track {
id: 4,
media_type: TrackMediaType::Video(VideoSettings {}),
direction: TrackDirection::Send(vec![4]),
}
],
};
// .------user1------. .------SFU------. .------user2-----.
// : pc_id = 1 o--->---o-->---->---->--o--->---o pc_id = 2 :
// : : : : : :
// : pc_id = 4 o---<---o--<----<-----<-o---<---o pc_id = 3 :
// '-----------------' '---------------' '----------------'
}
//-------------------------------------------------------------------------------------------------
//
//
// .-------user2------.
// .-------SFU-------. .-->--o pc_id = 2 :
// .------user1------. : .--->-->--o-->-' '------------------'
// : pc_id = 1 o-->-->-o--->---: :
// '-----------------' : '--->-->--o-->-. .-------user3------.
// '-----------------' '-->--o pc_id = 3 :
// '------------------'
fn one_pub_two_sub_sfu() {
// user1 enters room
// server => user1
AddPeer {
peer: Peer {
peer_id: 1,
p2p: false,
tracks: vec![
Track {
id: 1,
media_type: TrackMediaType::Audio(AudioSettings {}),
direction: TrackDirection::Send(vec![]),
},
Track {
id: 2,
media_type: TrackMediaType::Video(VideoSettings {}),
direction: TrackDirection::Send(vec![]),
}],
},
sdp_offer: Some("server_user1_recvonly_offer".to_owned()),
};
// user1 => server
Answer {
peer_id: 1,
remote_peer_id: None,
sdp_answer: "user_1_sendonly_answer".to_owned(),
};
// .-------SFU-------.
// .------user1------. : ;
// : pc_id = 1 o-->-->-o :
// '-----------------' : ;
// '-----------------'
// server => user2
AddPeer {
peer: Peer {
peer_id: 2,
p2p: false,
tracks: vec![
Track {
id: 1,
media_type: TrackMediaType::Audio(AudioSettings {}),
direction: TrackDirection::Recv(1),
},
Track {
id: 2,
media_type: TrackMediaType::Video(VideoSettings {}),
direction: TrackDirection::Recv(1),
}
],
},
sdp_offer: Some("server_user2_sendonly_offer".to_owned()),
};
//user2 => server
Answer {
peer_id: 1,
remote_peer_id: None,
sdp_answer: "user_2_recvonly_answer".to_owned(),
};
// .-------user2------.
// .-------SFU-------. .-->--o pc_id = 2 :
// .------user1------. : .---->----o-->-' '------------------'
// : pc_id = 1 o-->-->-o--->---' :
// '-----------------' : :
// '-----------------'
// server => user3
AddPeer {
peer: Peer {
peer_id: 3,
p2p: false,
tracks: vec![
Track {
id: 1,
media_type: TrackMediaType::Audio(AudioSettings {}),
direction: TrackDirection::Recv(1),
},
Track {
id: 2,
media_type: TrackMediaType::Video(VideoSettings {}),
direction: TrackDirection::Recv(1),
}
],
},
sdp_offer: Some("server_user3_sendonly_offer".to_owned()),
};
//user3 => server
Answer {
peer_id: 3,
remote_peer_id: None,
sdp_answer: "user_3_recvonly_answer".to_owned(),
};
//server => user1
UpdateTracks {
peer_id: 1,
tracks: vec![
Track {
id: 1,
media_type: TrackMediaType::Audio(AudioSettings {}),
direction: TrackDirection::Send(vec![2, 3]),
},
Track {
id: 2,
media_type: TrackMediaType::Video(VideoSettings {}),
direction: TrackDirection::Send(vec![2, 3]),
}],
};
// .-------user2------.
// .-------SFU-------. .-->--o pc_id = 2 :
// .------user1------. : .---->----o-->-' '------------------'
// : pc_id = 1 o-->-->-o--->---: :
// '-----------------' : '---->----o-->-. .-------user3------.
// '-----------------' '-->--o pc_id = 3 :
// '------------------'
}
fn one_pub_two_sub_sfu_unpublish_publish() {
// having
Peer {
peer_id: 1,
p2p: false,
tracks: vec![
Track {
id: 1,
media_type: TrackMediaType::Audio(AudioSettings {}),
direction: TrackDirection::Send(vec![2, 3]),
},
Track {
id: 2,
media_type: TrackMediaType::Video(VideoSettings {}),
direction: TrackDirection::Send(vec![2, 3]),
}],
};
Peer {
peer_id: 2,
p2p: false,
tracks: vec![
Track {
id: 1,
media_type: TrackMediaType::Audio(AudioSettings {}),
direction: TrackDirection::Recv(1),
},
Track {
id: 2,
media_type: TrackMediaType::Video(VideoSettings {}),
direction: TrackDirection::Recv(1),
}
],
};
Peer {
peer_id: 3,
p2p: false,
tracks: vec![
Track {
id: 1,
media_type: TrackMediaType::Audio(AudioSettings {}),
direction: TrackDirection::Recv(1),
},
Track {
id: 2,
media_type: TrackMediaType::Video(VideoSettings {}),
direction: TrackDirection::Recv(1),
}
],
};
// .-------user2------.
// .-------SFU-------. .-->--o pc_id = 2 :
// .------user1------. : .---->----o-->-' '------------------'
// : pc_id = 1 o-->-->-o--->---: :
// '-----------------' : '---->----o-->-. .-------user3------.
// '-----------------' '-->--o pc_id = 3 :
// '------------------'
// -----------------------Unpulbish all------------------------------
//user1 => server
RemoveTracks {
peer_id: 1,
tracks: vec![1, 2],
};
// server => user1
RemoveTracks {
peer_id: 1,
tracks: vec![1, 2],
};
//server => user2
RemoveTracks {
peer_id: 2,
tracks: vec![1, 2],
};
//server => user3
RemoveTracks {
peer_id: 3,
tracks: vec![1, 2],
};
// .-------user2------.
// .-------SFU-------. : pc_id = 2 :
// .------user1------. : : '------------------'
// : pc_id = 1 : : :
// '-----------------' : : .-------user3------.
// '-----------------' : pc_id = 3 :
// '------------------'
// -----------------------Unpulbish to user3----------------------------
// user1 => server
UpdateTracks {
peer_id: 1,
tracks: vec![
Track {
id: 1,
media_type: TrackMediaType::Audio(AudioSettings {}),
direction: TrackDirection::Send(vec![2]),
},
Track {
id: 2,
media_type: TrackMediaType::Video(VideoSettings {}),
direction: TrackDirection::Send(vec![2]),
}],
};
// server => user1
UpdateTracks {
peer_id: 1,
tracks: vec![
Track {
id: 1,
media_type: TrackMediaType::Audio(AudioSettings {}),
direction: TrackDirection::Send(vec![2]),
},
Track {
id: 2,
media_type: TrackMediaType::Video(VideoSettings {}),
direction: TrackDirection::Send(vec![2]),
}],
};
// server => user3
RemovePeer {
peer_id: 3
};
// .-------user2------.
// .-------SFU-------. .->-o pc_id = 2 :
// .------user1------. : .---->----o-->-' '------------------'
// : pc_id = 1 o-->-->-o--->---' :
// '-----------------' : : .-------user3------.
// '-----------------' : pc_id = 3 :
// '------------------'
}
// ------------------------------------------------------------------------------------------------
//
// .-----pub1------.
// : o--. .-----SFU-------. .------sub-------.
// '---------------' '-->--o->---------->--o-->--o :
// : : : :
// .------pub2-----. .-->--o->----------->-o-->--o :
// : o--' '---------------' '----------------'
// '---------------'
//
// ------------------------------------------------------------------------------------------------
//
// .----------SFU---------. .-----sub1-------.
// : .------o-->--o :
// .-----pub1------. : : : | :
// : peer_con_1----o-->--o------->--->---: .---o-->--o :
// '---------------' : .----+--' : '----------------'
// : : : : .-----sub2-------.
// .------pub2-----. : : '------o-->--o :
// : peer_con_1----o-->--o------>---: : | :
// '---------------' : '-----------o-->--o :
// '----------------------' '----------------'
// ------------------------------------------------------------------------------------------------ Протокол является достаточно гибким, позволяя реализовать, пожалуй, все варианты использования. Единственное что симулькаст не предусмотрен, но с ним пока все грустно - вменяемого апи нет, и все сводится к шаманству с SDP Одна из идей - стремление максимально упростить жизнь разработчикам, которые будут прикручивать разрабатываемый клиент к своему фронту. Как работают практически все SFU'шные room-management протоколы:
От разработчиков, взаимодейтсвующих с веб-клиентом требуется биндится к различным ивентам, самим дергать публикации, подписки, думать о настроках медиа. Чего хотелось достичь:
От пользователей клиента не будет требоваться ничего связанного с медиа или сигналлингом. Просто дергаем клиент чтобы тот подключился, дальше сервер с клиентом сами поднимут все необходимое. Но, естественно, будут случаи, в которых не обойтись без более ручного управиления. Необходимо чтобы публикующий мог выразить желание отключить видео/аудио для всех/кого-то конкретного. Подписчик должен иметь возможность отписаться от видео/аудио. Если есть возможность отменить публикацию/подписку, то необходимо предоставлять возможность переопубликовать/переподписаться. Если давать возможность переопубликовать/передописаться, то нужно предусмотреть механизмы предоставления пользователям текущего состяния комнаты - список потоков на которые они могут подписаться, список пользователей которым они могут опубликовать. В итоге получается гибрид из двух вариантов API. Для первого варианта достаточно следующих методов:
Для ручного управления уже понадобиться:
Некоторые методы( Предлагается начать с реализации первого варианта АПИ. Потом, по необходимости приступить к второму. Недостаток - сравнительная сложность. сс @tyranron , что думаете по этому варианту? |
Эту issue можно закрывать? |
@alexlapa nope, this is tracking issue, so it tracks relative RFC implementation. We should provide here in description the current state of how much RFC is implemented. We will close it when RFC is fully implemented. |
Тогда вижу смысл добавить лейбл tracking. |
Необходимо формализировать пользовательское API. Пока затрагивается только протокол взаимодейстивия с WebRTC пользователями.
API должно покрывать следующие use-case'ы:
В идеале, клиенту должно быть все-равно в каком режиме он работает. По результатам #4 было решено что
p2p = true
указывается только для публикующего.Накидаю реализации для следующих случаев:
Потом соберу одну подходящую под все варианты.
The text was updated successfully, but these errors were encountered: