|
|
|
|
@ -11,6 +11,7 @@
|
|
|
|
|
#include "../utun_instance.h" |
|
|
|
|
#include "../chat/chat_core.h" |
|
|
|
|
#include "../chat/chat_event.h" |
|
|
|
|
#include "../radio/radio.h" |
|
|
|
|
#include "stream_member.h" |
|
|
|
|
#include "stream_source.h" |
|
|
|
|
#include "stream_priv.h" |
|
|
|
|
@ -39,6 +40,7 @@ struct stream_session {
|
|
|
|
|
unsigned owners, relay_owners, holds; |
|
|
|
|
int anchored, decoded, closing, invalidated, finished, end_delivered, reason; |
|
|
|
|
int restarting, restart_gap, notify_close; |
|
|
|
|
int audio_enabled, radio_muted; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
struct STREAM_HANDLE { |
|
|
|
|
@ -193,6 +195,25 @@ static struct stream_session *session_find(struct stream_service *service, uint6
|
|
|
|
|
return NULL; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
int stream_radio_muted(struct UTUN_INSTANCE *inst, uint64_t group_id, uint64_t source_id) { |
|
|
|
|
struct stream_session *s = inst && inst->stream ? session_find(inst->stream, group_id, source_id) : NULL; |
|
|
|
|
return s && s->radio_muted; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Курсор означает просмотр. Raw readers, publisher и relay не отключают рацию. */ |
|
|
|
|
static void session_radio_update(struct stream_session *s) { |
|
|
|
|
int muted = 0; |
|
|
|
|
if (!s->closing && !s->invalidated && !s->finished && !s->source && s->audio_enabled) |
|
|
|
|
for (struct STREAM_HANDLE *h = s->service->handles; h; h = h->next) |
|
|
|
|
if (h->session == s && h->media_cursor && !h->relay && !h->publisher) { muted = 1; break; } |
|
|
|
|
if (muted == s->radio_muted) return; |
|
|
|
|
s->radio_muted = muted; |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_RADIO, "stream viewer mute group=%llx source=%llx epoch=%llx muted=%d audio=%d", |
|
|
|
|
(unsigned long long)s->descriptor.group_id, (unsigned long long)s->descriptor.source_node_id, |
|
|
|
|
(unsigned long long)s->descriptor.source_epoch, muted, s->audio_enabled); |
|
|
|
|
radio_refresh_member_mute(s->service->inst, s->descriptor.group_id, s->descriptor.source_node_id); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Две опции заменяются одной подписанной записью; список отражает экземпляры, не отдельные handles. */ |
|
|
|
|
static int service_publish_options(struct stream_service *service, struct TOPO_GROUP *group) { |
|
|
|
|
if (!group) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "stream options group missing"); return STREAM_ERR_STATE; } |
|
|
|
|
@ -212,7 +233,7 @@ static int service_publish_options(struct stream_service *service, struct TOPO_G
|
|
|
|
|
state.subscriptions[at] = state.subscriptions[at - 1]; at--; |
|
|
|
|
} |
|
|
|
|
state.subscriptions[at] = entry; |
|
|
|
|
if (s->source && !s->finished) { state.source = s->descriptor; state.published = 1; } |
|
|
|
|
if (s->source && !s->finished) { state.source = s->descriptor; state.published = 1; state.audio_enabled = s->audio_enabled; } |
|
|
|
|
} |
|
|
|
|
char source[MS_USERINFO_MAX + 1], subscriptions[MS_USERINFO_MAX + 1]; |
|
|
|
|
int result = stream_member_format(&state, source, subscriptions); |
|
|
|
|
@ -263,6 +284,7 @@ static void session_close(struct stream_session *s, int reason, int update_profi
|
|
|
|
|
if (s->closing) return; |
|
|
|
|
s->notify_close = notify && s->owners > s->relay_owners; |
|
|
|
|
s->closing = 1; s->reason = reason; s->owners = 0; |
|
|
|
|
session_radio_update(s); |
|
|
|
|
stream_optimizer_destroy(s->optimizer); s->optimizer = NULL; |
|
|
|
|
stream_network_cancel(s->service->network, &s->descriptor); |
|
|
|
|
for (struct STREAM_HANDLE *h = s->service->handles; h; h = h->next) |
|
|
|
|
@ -501,6 +523,8 @@ static void member_changed(uint64_t node_id, const char *tags, const char *chann
|
|
|
|
|
|| state.source.block_size != s->descriptor.block_size)))) { |
|
|
|
|
s->invalidated = 1; s->reason = STREAM_CLOSE_SOURCE_CHANGED; |
|
|
|
|
} |
|
|
|
|
if (!own && result == STREAM_OK) s->audio_enabled = state.published && state.audio_enabled; |
|
|
|
|
session_radio_update(s); |
|
|
|
|
} |
|
|
|
|
if (!changed || service->reconcile) return; |
|
|
|
|
service->reconcile = uasync_call_soon(service->inst->ua, service, service_reconcile); |
|
|
|
|
@ -594,7 +618,7 @@ void stream_set_event_callback(struct UTUN_INSTANCE *inst, stream_event_fn callb
|
|
|
|
|
|
|
|
|
|
/* Подготовить все ресурсы до публикации member-опций; rollback не оставляет видимую подписку. */ |
|
|
|
|
static int session_open(struct UTUN_INSTANCE *inst, const struct stream_descriptor *d, const uint8_t key[32], int publisher, int automatic, |
|
|
|
|
int relay, |
|
|
|
|
int relay, int audio_enabled, |
|
|
|
|
struct STREAM_HANDLE **out) { |
|
|
|
|
struct stream_service *service = inst->stream; |
|
|
|
|
struct stream_session *s = session_find(service, d->group_id, d->source_node_id); |
|
|
|
|
@ -609,6 +633,7 @@ static int session_open(struct UTUN_INSTANCE *inst, const struct stream_descript
|
|
|
|
|
s = u_calloc(1, sizeof(*s)); |
|
|
|
|
if (!s) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "stream session allocation failed"); u_free(h); return STREAM_ERR_MEMORY; } |
|
|
|
|
s->service = service; s->descriptor = *d; memcpy(s->public_key, key, 32); |
|
|
|
|
s->audio_enabled = audio_enabled; |
|
|
|
|
s->output = u_malloc((uint32_t)stream_input_size(d)); |
|
|
|
|
s->metrics = u_calloc(d->n, sizeof(*s->metrics)); |
|
|
|
|
int result = s->output && s->metrics ? STREAM_OK : STREAM_ERR_MEMORY; |
|
|
|
|
@ -688,14 +713,14 @@ int stream_publish_open(struct UTUN_INSTANCE *inst, uint64_t group_id, const str
|
|
|
|
|
struct stream_session *previous = session_find(inst->stream, group_id, inst->node_id); |
|
|
|
|
if (previous && previous->source && previous->end_valid) |
|
|
|
|
session_close(previous, STREAM_CLOSE_SOURCE_CHANGED, 0, 1); |
|
|
|
|
return session_open(inst, &d, inst->my_ed25519_pubkey, 1, 0, 0, out); |
|
|
|
|
return session_open(inst, &d, inst->my_ed25519_pubkey, 1, 0, 0, 0, out); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static int subscribe_open(struct UTUN_INSTANCE *inst, uint64_t group_id, uint64_t source_id, int automatic, struct STREAM_HANDLE **out) { |
|
|
|
|
int result = stream_open_check(inst, group_id, out); |
|
|
|
|
if (result != STREAM_OK) return result; |
|
|
|
|
struct stream_session *s = session_find(inst->stream, group_id, source_id); |
|
|
|
|
if (s) return session_open(inst, &s->descriptor, s->public_key, 0, automatic, 0, out); |
|
|
|
|
if (s) return session_open(inst, &s->descriptor, s->public_key, 0, automatic, 0, s->audio_enabled, out); |
|
|
|
|
struct stream_member_state state; uint8_t key[32]; |
|
|
|
|
result = stream_member_load(inst, group_id, source_id, &state, key); |
|
|
|
|
if (result != STREAM_OK) return result; |
|
|
|
|
@ -703,7 +728,7 @@ static int subscribe_open(struct UTUN_INSTANCE *inst, uint64_t group_id, uint64_
|
|
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_STREAM, "source not yet published source=%llx", (unsigned long long)source_id); |
|
|
|
|
return STREAM_WAIT; |
|
|
|
|
} |
|
|
|
|
return session_open(inst, &state.source, key, 0, automatic, 0, out); |
|
|
|
|
return session_open(inst, &state.source, key, 0, automatic, 0, state.audio_enabled, out); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Запрос старой эпохи не может незаметно подписать суперузел на новый запуск. */ |
|
|
|
|
@ -712,7 +737,7 @@ int stream_relay_open(struct UTUN_INSTANCE *inst, const struct stream_descriptor
|
|
|
|
|
if (result != STREAM_OK) return result; |
|
|
|
|
struct stream_session *s = session_find(inst->stream, key->group_id, key->source_node_id); |
|
|
|
|
if (s && s->descriptor.source_epoch == key->source_epoch) |
|
|
|
|
return session_open(inst, &s->descriptor, s->public_key, 0, 1, 1, out); |
|
|
|
|
return session_open(inst, &s->descriptor, s->public_key, 0, 1, 1, s->audio_enabled, out); |
|
|
|
|
struct stream_member_state state; uint8_t public_key[32]; |
|
|
|
|
result = stream_member_load(inst, key->group_id, key->source_node_id, &state, public_key); |
|
|
|
|
if (result != STREAM_OK) return result; |
|
|
|
|
@ -721,7 +746,7 @@ int stream_relay_open(struct UTUN_INSTANCE *inst, const struct stream_descriptor
|
|
|
|
|
(unsigned long long)key->source_node_id, (unsigned long long)key->source_epoch, state.published, |
|
|
|
|
(unsigned long long)state.source.source_epoch); return STREAM_ERR_STATE; |
|
|
|
|
} |
|
|
|
|
return session_open(inst, &state.source, public_key, 0, 1, 1, out); |
|
|
|
|
return session_open(inst, &state.source, public_key, 0, 1, 1, state.audio_enabled, out); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
int stream_subscribe_open(struct UTUN_INSTANCE *inst, uint64_t group_id, uint64_t source_id, struct STREAM_HANDLE **out) { |
|
|
|
|
@ -758,6 +783,7 @@ void stream_close(struct STREAM_HANDLE *h) {
|
|
|
|
|
} |
|
|
|
|
struct stream_session *s = h->session; |
|
|
|
|
if (s) { |
|
|
|
|
session_radio_update(s); |
|
|
|
|
int was_relay = s->owners == s->relay_owners; |
|
|
|
|
if ((h->publisher && !s->end_valid) || s->owners == 1) session_close(s, STREAM_CLOSE_STOPPED, 1, !h->relay); |
|
|
|
|
else { |
|
|
|
|
@ -804,6 +830,7 @@ int stream_media_enable(struct STREAM_HANDLE *h, unsigned tracks) {
|
|
|
|
|
if (tracks && !h->media_cursor) { |
|
|
|
|
int result = stream_media_cursor_create(&h->media_cursor, s->media, tracks); |
|
|
|
|
if (result != STREAM_OK) return result; |
|
|
|
|
session_radio_update(s); |
|
|
|
|
} |
|
|
|
|
session_media_schedule(s); |
|
|
|
|
return STREAM_OK; |
|
|
|
|
@ -898,6 +925,27 @@ int stream_publish_block(struct STREAM_HANDLE *h, const uint8_t *data, size_t le
|
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
int stream_publish_set_audio_enabled(struct STREAM_HANDLE *h, int enabled) { |
|
|
|
|
if (!stream_handle_active(h) || !h->publisher || h->session->finished || (enabled != 0 && enabled != 1)) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "stream audio change invalid publisher/state enabled=%d", enabled); return STREAM_ERR_STATE; |
|
|
|
|
} |
|
|
|
|
struct stream_session *s = h->session; |
|
|
|
|
if (s->audio_enabled == enabled) return STREAM_OK; |
|
|
|
|
s->holds++; |
|
|
|
|
int previous = s->audio_enabled; |
|
|
|
|
s->audio_enabled = enabled; |
|
|
|
|
int result = service_publish_options(s->service, stream_group(s->service->inst, s->descriptor.group_id)); |
|
|
|
|
if (result != STREAM_OK || s->closing) { |
|
|
|
|
s->audio_enabled = previous; |
|
|
|
|
if (result == STREAM_OK) result = STREAM_ERR_STATE; |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "stream audio state publication failed result=%d", result); |
|
|
|
|
} else DEBUG_INFO(DEBUG_CATEGORY_STREAM, "stream audio group=%llx epoch=%llx enabled=%d", |
|
|
|
|
(unsigned long long)s->descriptor.group_id, (unsigned long long)s->descriptor.source_epoch, enabled); |
|
|
|
|
s->holds--; |
|
|
|
|
if (s->closing && !s->holds && !s->delivery) session_free(s); |
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
int stream_publish_finish(struct STREAM_HANDLE *h) { |
|
|
|
|
if (!stream_handle_active(h) || !h->publisher) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "stream finish not a publisher"); return STREAM_ERR_STATE; } |
|
|
|
|
struct stream_session *s = h->session; |
|
|
|
|
|