Performance:

- Adding streams to a channel (per-row "Add to Channel" button and bulk "Add selected to Channel") now completes in a single PATCH request instead of three. Previously each operation called `API.updateChannel` (which internally triggered `requeryStreams`), then `requeryChannels()`. Both paths now use a dedicated `API.addStreamsToChannel()` method that issues only the PATCH, merges the existing channel streams with the newly added stream objects locally, and updates the store in-place, no `requeryStreams` or `requeryChannels` round-trips.
  - Removed an unnecessary `requeryStreams()` call from `API.createChannelFromStream()`. Stream data does not change when a channel is created from it; the caller already calls `requeryChannels()` to display the new channel.
This commit is contained in:
SergeantPanda 2026-04-19 10:51:35 -05:00
parent e1e60aa056
commit e7cb13e8b0
3 changed files with 56 additions and 20 deletions

View file

@ -25,6 +25,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `DraggableRow` (stream rows inside the expanded channel) is now wrapped in `React.memo` with a comparator on `row.original` identity and `index`, so rows whose stream data and position haven't changed are skipped entirely during re-renders caused by a sibling row moving.
- Removed dead code in the `name` column cell: `playlists[stream.m3u_account]?.name` was indexing an array by an integer ID, which always returns `undefined`, the value was immediately overridden by `m3uAccountsMap`. The dead access and its fallback variable are gone; `m3uAccountsMap` is now the sole lookup.
- Removed spurious `state: { data }` from the `useReactTable` call. `data` is a root-level TanStack Table option, not a controlled-state entry; passing it in `state` was a no-op but misleading.
- Adding streams to a channel (per-row "Add to Channel" button and bulk "Add selected to Channel") now completes in a single PATCH request instead of three. Previously each operation called `API.updateChannel` (which internally triggered `requeryStreams`), then `requeryChannels()`. Both paths now use a dedicated `API.addStreamsToChannel()` method that issues only the PATCH, merges the existing channel streams with the newly added stream objects locally, and updates the store in-place, no `requeryStreams` or `requeryChannels` round-trips.
- Removed an unnecessary `requeryStreams()` call from `API.createChannelFromStream()`. Stream data does not change when a channel is created from it; the caller already calls `requeryChannels()` to display the new channel.
- Eliminated repeated DB queries in the `ts_proxy` hot path. `StreamManager`, `StreamGenerator`, and `ProxyServer` were each calling `Channel.objects.get(uuid=...)` on every retry, reconnect, failover, and buffering event solely to retrieve `channel.name` for log events. `StreamManager` and `StreamGenerator` now fetch the channel name once at construction via a lightweight `values_list` query and store it as `self.channel_name`. `ProxyServer` caches the name in a `_channel_names` dict keyed by channel ID at channel-start time and pops it at channel-stop time. (Fixes #1138)
### Changed

View file

@ -638,7 +638,7 @@ export default class API {
}
/**
* Lightweight stream reorder: PATCHes only the stream order for a channel
* PATCHes only the stream order for a channel
* without triggering requeryStreams or requeryChannels. The caller is
* responsible for optimistic UI updates.
*/
@ -666,6 +666,48 @@ export default class API {
}
}
/**
* PATCHes the channel with the
* combined stream list and updates the channelsTable store in-place
* using the stream objects the caller already has. Skips requeryStreams
* (stream data doesn't change) and requeryChannels (we build the
* result locally).
*
* @param {number} channelId
* @param {Array} existingStreams - current channel.streams (full objects)
* @param {Array} newStreams - stream objects to append
*/
static async addStreamsToChannel(channelId, existingStreams, newStreams) {
try {
const existing = existingStreams || [];
// Deduplicate by ID, preserving order (existing first, new appended)
const seen = new Set(existing.map((s) => s.id));
const merged = [...existing];
for (const s of newStreams) {
if (!seen.has(s.id)) {
seen.add(s.id);
merged.push(s);
}
}
await request(`${host}/api/channels/channels/${channelId}/`, {
method: 'PATCH',
body: { id: channelId, streams: merged.map((s) => s.id) },
});
// Update the channelsTable store in-place with the merged streams
const store = useChannelsTableStore.getState();
const channel = store.channels.find((c) => c.id === channelId);
if (channel) {
store.updateChannel({ ...channel, streams: merged });
}
} catch (e) {
errorNotification('Failed to add streams to channel', e);
// On failure, requery to restore correct state
await API.requeryChannels();
}
}
static async updateChannels(ids, values) {
const body = [];
for (const id of ids) {
@ -898,7 +940,6 @@ export default class API {
useChannelsStore.getState().addChannel(response);
}
await API.requeryStreams();
return response;
} catch (e) {
errorNotification('Failed to create channel', e);

View file

@ -89,15 +89,9 @@ const StreamRowActions = ({
);
const addStreamToChannel = async () => {
await API.updateChannel({
id: targetChannelId,
streams: [
...new Set(
channelSelectionStreams.map((s) => s.id).concat([row.original.id])
),
],
});
await API.requeryChannels();
await API.addStreamsToChannel(targetChannelId, channelSelectionStreams, [
row.original,
]);
};
const onEdit = useCallback(() => {
@ -1040,15 +1034,14 @@ const StreamsTable = ({ onReady }) => {
};
const addStreamsToChannel = async () => {
await API.updateChannel({
id: targetChannelId,
streams: [
...new Set(
channelSelectionStreams.map((s) => s.id).concat(selectedStreamIds)
),
],
});
await API.requeryChannels();
// Look up full stream objects from the current page data
const selectedIdSet = new Set(selectedStreamIds);
const newStreams = data.filter((s) => selectedIdSet.has(s.id));
await API.addStreamsToChannel(
targetChannelId,
channelSelectionStreams,
newStreams
);
};
const onRowSelectionChange = (updatedIds) => {