Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: channel pub-sub feature and tests. cluster integration test #262

Merged
merged 2 commits into from
Apr 24, 2024

Conversation

giangndm
Copy link
Contributor

@giangndm giangndm commented Apr 24, 2024

Pull Request

Description

This PR implement logic for pubsub channel with feedback: Bitrate Limit and Key Frame request. This also added more tests

Related Issue

If this pull request is related to any issue, please mention it here.

Checklist

  • I have tested the changes locally.
  • I have reviewed the code changes.
  • I have updated the documentation, if necessary.
  • I have added appropriate tests, if applicable.

Screenshots

If applicable, add screenshots to help explain the changes made.

Additional Notes

Add any additional notes or context about the pull request here.

Summary by CodeRabbit

  • Documentation
    • Improved sentence clarity and corrected typos in the README file.
  • New Features
    • Enhanced media server protocols with new identifier generation functions.
    • Introduced new feedback mechanisms for media bitrate and keyframe requests.
    • Added handling for different room outputs in media clusters.
  • Bug Fixes
    • Fixed room destruction handling with a new destroyed flag.
  • Refactor
    • Updated media cluster structures to use more precise data types and improved logic flow.
    • Refined methods for media track control and replaced direct data structures with more structured types for better management and feedback handling.

Copy link

coderabbitai bot commented Apr 24, 2024

Walkthrough

Walkthrough

The update enhances the media core package by refining data types, introducing new enums, and restructuring logic for better performance and scalability. Changes include type adjustments for bitrate fields, new identifier generation functions, and improved feedback handling mechanisms. This update aims for more robust and efficient media clustering functionalities.

Changes

File Path Change Summary
README.md Typo fixes and sentence clarity improvements.
.../src/cluster.rs, .../id_generator.rs Updated types for bitrate fields, added unique ID functions, and new room output handling.
.../room.rs, .../channel_pub.rs, .../channel_sub.rs Enhanced room and channel management with new enums, logic restructuring, and feedback mechanisms.
.../metadata.rs Integrated new ID generation functions and updated key generation for peers and tracks.

🐰✨
In the land of code and cluster,
A rabbit hopped, with no fluster.
Bitrates high and feedback sound,
In rooms restructured, bugs unbound.
Cheers to changes, wise and new,
With every commit, our project grew!
🌟🚀


Recent Review Details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits Files that changed from the base of the PR and between fd703ca and 0d25929.
Files selected for processing (7)
  • README.md (1 hunks)
  • packages/media_core/src/cluster.rs (6 hunks)
  • packages/media_core/src/cluster/id_generator.rs (1 hunks)
  • packages/media_core/src/cluster/room.rs (9 hunks)
  • packages/media_core/src/cluster/room/channel_pub.rs (3 hunks)
  • packages/media_core/src/cluster/room/channel_sub.rs (3 hunks)
  • packages/media_core/src/cluster/room/metadata.rs (25 hunks)
Additional Context Used
LanguageTool (74)
README.md (74)

Near line 31: Unpaired symbol: ‘]’ seems to be missing
Context: ...twork architecture, please refer to our [Smart-Routing](https://github.com/8xFF/a...


Near line 36: Possible spelling mistake found.
Context: ... is a demo video of the version used by Bluesea Network) ## Project Status: Refactorin...


Near line 40: Possible spelling mistake found.
Context: ...re media server and network stack with [sans-io-runtime](https://github.com/8xff/sans-io-runtim...


Near line 51: Possible spelling mistake found.
Context: ..., or Kubernetes - Easy to scale: global pubsub network, similar to [Cloudflare interco...


Near line 51: Unpaired symbol: ‘(’ seems to be missing
Context: ...dflare.com/announcing-cloudflare-calls/)) | Feature | Description ...


Near line 53: Possible typo: you repeated a whitespace
Context: ...nnouncing-cloudflare-calls/)) | Feature | Description ...


Near line 53: Possible typo: you repeated a whitespace
Context: .../)) | Feature | Description | Status | | ------------------- | -----...


Near line 55: Possible spelling mistake found.
Context: ...i-zones Cluster | Implement with global pubsub network [RFC-0003](https://github.com/8...


Near line 55: Possible typo: you repeated a whitespace
Context: ...ttps://github.com/8xFF/rfcs/pull/3) | 🚧 | | Whip | Whip Protocol ...


Near line 56: Possible typo: you repeated a whitespace
Context: ....com/8xFF/rfcs/pull/3) | 🚧 | | Whip | Whip Protocol ...


Near line 56: Possible typo: you repeated a whitespace
Context: ... | | Whip | Whip Protocol | 🚧 | | Whep | Whep ...


Near line 56: Possible typo: you repeated a whitespace
Context: ... | 🚧 | | Whep | Whep Protocol ...


Near line 57: Possible spelling mistake found.
Context: ... | 🚧 | | Whep | Whep Protocol ...


Near line 57: Possible typo: you repeated a whitespace
Context: ... | 🚧 | | Whep | Whep Protocol ...


Near line 57: Possible spelling mistake found.
Context: ... | 🚧 | | Whep | Whep Protocol ...


Near line 57: Possible typo: you repeated a whitespace
Context: ... | | Whep | Whep Protocol | 🚧 | | WebRTC-SDK | Webrt...


Near line 57: Possible typo: you repeated a whitespace
Context: ... | 🚧 | | WebRTC-SDK | Webrtc-SDK Pro...


Near line 58: Possible typo: you repeated a whitespace
Context: ... | 🚧 | | WebRTC-SDK | Webrtc-SDK Protocol [RFC-0005](https:/...


Near line 58: Possible spelling mistake found.
Context: ... | 🚧 | | WebRTC-SDK | Webrtc-SDK Protocol [RFC-0005](https://github.com/...


Near line 58: Possible typo: you repeated a whitespace
Context: ...05](8xFF/rfcs#5) | ❌ | | RTMP | RTMP P...


Near line 58: Possible typo: you repeated a whitespace
Context: ...m/8xFF/rfcs/pull/5) | ❌ | | RTMP | RTMP Protocol ...


Near line 59: Possible spelling mistake found.
Context: ...cs/pull/5) | ❌ | | RTMP | RTMP Protocol ...


Near line 59: Possible typo: you repeated a whitespace
Context: ...ull/5) | ❌ | | RTMP | RTMP Protocol ...


Near line 59: Possible typo: you repeated a whitespace
Context: ... | | RTMP | RTMP Protocol | ❌ | | RTMP-Transcode | RTMP w...


Near line 59: Possible typo: you repeated a whitespace
Context: ... | ❌ | | RTMP-Transcode | RTMP with Tran...


Near line 60: Possible spelling mistake found.
Context: ... | ❌ | | RTMP-Transcode | RTMP with Transcode ...


Near line 60: Possible typo: you repeated a whitespace
Context: ... | ❌ | | RTMP-Transcode | RTMP with Transcode ...


Near line 60: Possible typo: you repeated a whitespace
Context: ...TMP-Transcode | RTMP with Transcode | ❌ | | SIP | SIP ca...


Near line 60: Possible typo: you repeated a whitespace
Context: ... | ❌ | | SIP | SIP calls ...


Near line 61: Possible typo: you repeated a whitespace
Context: ... | ❌ | | SIP | SIP calls ...


Near line 61: Possible typo: you repeated a whitespace
Context: ... | | SIP | SIP calls | ❌ | | MoQ | Media-...


Near line 61: Possible typo: you repeated a whitespace
Context: ... | ❌ | | MoQ | Media-over-Qui...


Near line 62: Possible spelling mistake found.
Context: ... | ❌ | | MoQ | Media-over-Quic ...


Near line 62: Possible typo: you repeated a whitespace
Context: ... | ❌ | | MoQ | Media-over-Quic ...


Near line 62: Possible spelling mistake found.
Context: ... | ❌ | | MoQ | Media-over-Quic ...


Near line 62: Possible typo: you repeated a whitespace
Context: ... | MoQ | Media-over-Quic | ❌ | | Monitoring | Dashbo...


Near line 62: Possible typo: you repeated a whitespace
Context: ... | ❌ | | Monitoring | Dashboard for ...


Near line 63: Possible typo: you repeated a whitespace
Context: ... | ❌ | | Monitoring | Dashboard for monitoring ...


Near line 63: Possible typo: you repeated a whitespace
Context: ...ring | Dashboard for monitoring | ❌ | | Recording | Record...


Near line 63: Possible typo: you repeated a whitespace
Context: ... | ❌ | | Recording | Record stream ...


Near line 64: Possible typo: you repeated a whitespace
Context: ... | ❌ | | Recording | Record stream ...


Near line 64: Possible typo: you repeated a whitespace
Context: ... | | Recording | Record stream | ❌ | | Gateway | Extern...


Near line 64: Possible typo: you repeated a whitespace
Context: ... | ❌ | | Gateway | External gatew...


Near line 65: Possible typo: you repeated a whitespace
Context: ... | ❌ | | Gateway | External gateway [RFC-0003](https://gi...


Near line 65: Possible typo: you repeated a whitespace
Context: ...03](8xFF/rfcs#3) | ❌ | | Connector | Extern...


Near line 65: Possible typo: you repeated a whitespace
Context: ...xFF/rfcs/pull/3) | ❌ | | Connector | External event...


Near line 66: Possible typo: you repeated a whitespace
Context: ... | ❌ | | Connector | External event handling ...


Near line 66: Possible typo: you repeated a whitespace
Context: ...ctor | External event handling | ❌ | Status: - ❌: Not started - ?...


Near line 66: Possible typo: you repeated a whitespace
Context: ... | ❌ | Status: - ❌: Not started - 🚧: In pr...


Near line 87: Possible typo: you repeated a whitespace
Context: ...er --help ``` - Download prebuild | OS | Arch | Link ...


Near line 87: Possible typo: you repeated a whitespace
Context: ...``` - Download prebuild | OS | Arch | Link ...


Near line 87: Possible typo: you repeated a whitespace
Context: ... prebuild | OS | Arch | Link | | ----- | ------------ | -------------...


Near line 89: The operating system from Apple is written “macOS”.
Context: ...----------------------------------- | | MacOS | aarch64 | [Download](https://git...


Near line 89: Possible typo: you repeated a whitespace
Context: ...-------------------- | | MacOS | aarch64 | [Download](https://github.com/8xFF/atm...


Near line 89: Possible typo: you repeated a whitespace
Context: ...atm0s-media-server-aarch64-apple-darwin) | | MacOS | x86_64 | [Download](ht...


Near line 90: The operating system from Apple is written “macOS”.
Context: ...-server-aarch64-apple-darwin) | | MacOS | x86_64 | [Download](https://git...


Near line 90: Possible typo: you repeated a whitespace
Context: ...4-apple-darwin) | | MacOS | x86_64 | [Download](https://github.com/8xFF/atm...


Near line 90: Possible typo: you repeated a whitespace
Context: .../atm0s-media-server-x86_64-apple-darwin) | | Linux | aarch64 gnu | [Download](ht...


Near line 91: Please check whether ‘knew’ (past of ‘know’) might be the correct word here instead of ‘gnu’ (large African antelope).
Context: ...pple-darwin) | | Linux | aarch64 gnu | [Download](https://github.com/8xFF/a...


Near line 91: Possible typo: you repeated a whitespace
Context: ...e-darwin) | | Linux | aarch64 gnu | [Download](https://github.com/8xFF/atm...


Near line 91: Possible typo: you repeated a whitespace
Context: ...-media-server-aarch64-unknown-linux-gnu) | | Linux | x86_64 gnu | [Download](ht...


Near line 92: Please check whether ‘knew’ (past of ‘know’) might be the correct word here instead of ‘gnu’ (large African antelope).
Context: ...-unknown-linux-gnu) | | Linux | x86_64 gnu | [Download](https://github.com/8xFF/...


Near line 92: Possible typo: you repeated a whitespace
Context: ...known-linux-gnu) | | Linux | x86_64 gnu | [Download](https://github.com/8xFF/atm...


Near line 92: Possible typo: you repeated a whitespace
Context: ...s-media-server-x86_64-unknown-linux-gnu) | | Linux | aarch64 musl | [Download](ht...


Near line 93: Possible spelling mistake found.
Context: ...nknown-linux-gnu) | | Linux | aarch64 musl | [Download](https://github.com/8xFF/at...


Near line 94: Possible spelling mistake found.
Context: ...-unknown-linux-musl) | | Linux | x86_64 musl | [Download](https://github.com/8xFF/a...


Near line 94: Possible typo: you repeated a whitespace
Context: ...nown-linux-musl) | | Linux | x86_64 musl | [Download](https://github.com/8xFF/atm...


Near line 94: Possible typo: you repeated a whitespace
Context: ...-media-server-x86_64-unknown-linux-musl) | - Build from source ``` cargo build ...


Near line 111: Possible spelling mistake found.
Context: ...it will print out the node address like 101@/ip4/192.168.1.10/udp/10101/ip4/192.168.1.10/tcp/10101, you ...


Near line 125: Possible spelling mistake found.
Context: ...02/samples/whep/whep.html You can use [Pregenerated-Token](./docs/getting-started/quick-start/whi...


Near line 144: Consider a different adverb to strengthen your wording.
Context: ...usly being improved and updated. We are always looking for ways to make it better, whe...


Near line 144: Consider using a shorter alternative to avoid wordiness.
Context: ...ated. We are always looking for ways to make it better, whether that's through optimizing perf...


Near line 144: Consider a different adverb to strengthen your wording.
Context: ...ontributions from the community and are always looking for new ideas and suggestions. ...


Near line 146: Unpaired symbol: ‘]’ seems to be missing
Context: ...ntributor-guide/README.md) and join our [Discord channel](https://discord.gg/qXr5...

GitHub Check Runs (1)
clippy success (2)

packages/media_core/src/cluster/room/channel_sub.rs: [warning] 100-100:
use of or_insert to construct default value


packages/media_core/src/cluster/room/channel_sub.rs: [warning] 136-136:
using clone on type Feedback which implements the Copy trait

Additional comments not posted (16)
packages/media_core/src/cluster/id_generator.rs (1)

8-42: All identifier generation functions are implemented correctly and use appropriate hashing mechanisms.

packages/media_core/src/cluster/room/channel_pub.rs (3)

20-34: The implementation of FeedbackKind and its conversion from Feedback are correctly handled and match the described functionality.


60-66: The on_channel_feedback method correctly processes different types of feedback using the FeedbackKind enum.


73-73: The update to access gen_channel_id through the id_generator module is a good structural improvement.

README.md (1)

40-40: The corrections and improvements in the README enhance clarity and fix typographical errors.

packages/media_core/src/cluster.rs (3)

50-57: The update to use u64 for bitrate fields in ClusterRemoteTrackEvent and ClusterLocalTrackControl is appropriate for handling larger values.


103-103: The modification of the rooms field to use TaskGroup is a positive change, likely improving task management and scalability.


150-158: The implementation of process_room_output method in MediaCluster is well-done, enhancing output handling and modularity.

packages/media_core/src/cluster/room.rs (4)

86-99: > 📝 NOTE

This review was outside the diff hunks and was mapped to the diff hunk with the greatest overlap. Original lines [51-96]

The restructuring of the ClusterRoom struct, including the addition of the destroyed flag and adjustments to methods, is well-implemented and improves lifecycle management.


30-34: The new variants added to the Output enum in ClusterRoom are appropriate and enhance the handling of different output types.


10-10: The removal of the gen_channel_id function from room.rs and centralizing it in the id_generator module is a good structural improvement.


201-205: The refinements in the control_local_track method in ClusterRoom are well-done, enhancing track control functionality.

packages/media_core/src/cluster/room/metadata.rs (4)

57-58: Ensure that the new methods id_generator::peers_map and id_generator::tracks_map are correctly implemented and tested, as they are crucial for the generation of map identifiers.


90-90: The generation of a peer key using id_generator::peers_key should be verified to ensure that it correctly handles different peer identifiers.


141-141: Verify the deletion logic in on_leave method to ensure that it correctly handles the removal of peer entries from the map.


185-185: In the on_subscribe_peer method, ensure that the subscription logic correctly handles cases where a peer is already subscribed to prevent duplicate entries.


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

Share
Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai generate interesting stats about this repository and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (invoked as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger a review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Configration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

subscribers.push((owner, track));
if subscribers.len() == 1 {
self.subscribers.insert((owner, track), (channel_id, target_peer, target_track));
let channel_container = self.channels.entry(channel_id).or_insert(Default::default());

Check warning

Code scanning / clippy

use of or_insert to construct default value Warning

use of or\_insert to construct default value
if let Some(sum_fb) = &mut sum_fb {
*sum_fb = *sum_fb + *fb;
} else {
sum_fb = Some(fb.clone());

Check warning

Code scanning / clippy

using clone on type Feedback which implements the Copy trait Warning

using clone on type Feedback which implements the Copy trait
Copy link

codecov bot commented Apr 24, 2024

Codecov Report

Attention: Patch coverage is 95.67430% with 17 lines in your changes are missing coverage. Please review.

Project coverage is 36.15%. Comparing base (fd703ca) to head (359df6a).

❗ Current head 359df6a differs from pull request most recent head 0d25929. Consider uploading reports for the commit 0d25929 to get more accurate results

Files Patch % Lines
...ackages/media_core/src/cluster/room/channel_sub.rs 95.12% 8 Missing ⚠️
packages/media_core/src/cluster.rs 92.10% 6 Missing ⚠️
packages/media_core/src/cluster/room.rs 83.33% 2 Missing ⚠️
...ackages/media_core/src/cluster/room/channel_pub.rs 98.55% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           master     #262       +/-   ##
===========================================
+ Coverage   23.60%   36.15%   +12.55%     
===========================================
  Files          36       37        +1     
  Lines        2779     3059      +280     
===========================================
+ Hits          656     1106      +450     
+ Misses       2123     1953      -170     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@giangndm giangndm marked this pull request as ready for review April 24, 2024 16:08
@giangndm giangndm merged commit 829ea8b into 8xFF:master Apr 24, 2024
8 checks passed
@github-actions github-actions bot mentioned this pull request Apr 23, 2024
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Out of diff range and nitpick comments (1)
packages/media_core/src/cluster/room/channel_sub.rs (1)

26-30: Consider adjusting the comment for KEYFRAME_FEEDBACK_INTERVAL to correctly reflect the interval of 1000 ms instead of 100 ms.

subscribers.push((owner, track));
if subscribers.len() == 1 {
self.subscribers.insert((owner, track), (channel_id, target_peer, target_track));
let channel_container = self.channels.entry(channel_id).or_insert(Default::default());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use of or_insert to construct default value: Consider using or_insert_with to lazily evaluate the default constructor only when needed.

- let channel_container = self.channels.entry(channel_id).or_insert(Default::default());
+ let channel_container = self.channels.entry(channel_id).or_insert_with(Default::default);

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
let channel_container = self.channels.entry(channel_id).or_insert(Default::default());
let channel_container = self.channels.entry(channel_id).or_insert_with(Default::default);

if let Some(sum_fb) = &mut sum_fb {
*sum_fb = *sum_fb + *fb;
} else {
sum_fb = Some(fb.clone());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid using clone on type Feedback which implements the Copy trait. Use direct assignment instead.

- sum_fb = Some(fb.clone());
+ sum_fb = Some(*fb);

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
sum_fb = Some(fb.clone());
sum_fb = Some(*fb);

Comment on lines +125 to +129
//clean if if timeout
channel_container
.bitrate_fbs
.retain(|_, (ts, _)| now.duration_since(*ts).as_millis() < BITRATE_FEEDBACK_TIMEOUT as u128);

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure that the feedback cleanup logic correctly handles the timeout. The current implementation might lead to premature removal of feedback entries due to the comparison of milliseconds and seconds.

- .retain(|_, (ts, _)| now.duration_since(*ts).as_millis() < BITRATE_FEEDBACK_TIMEOUT as u128);
+ .retain(|_, (ts, _)| now.duration_since(*ts).as_secs() < BITRATE_FEEDBACK_TIMEOUT as u64 / 1000);

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
//clean if if timeout
channel_container
.bitrate_fbs
.retain(|_, (ts, _)| now.duration_since(*ts).as_millis() < BITRATE_FEEDBACK_TIMEOUT as u128);
//clean if if timeout
channel_container
.bitrate_fbs
.retain(|_, (ts, _)| now.duration_since(*ts).as_secs() < BITRATE_FEEDBACK_TIMEOUT as u64 / 1000);

@giangndm giangndm deleted the feat-media-channel-sub-pub branch June 6, 2024 00:44
giangndm added a commit to giangndm/8xFF-decentralized-media-server that referenced this pull request Nov 26, 2024
…FF#262)

* feat: channel pub-sub feature and tests. cluster integration test

* chore: fix typos
This was referenced Feb 8, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant