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: Add videos/images from source with protocol extension #301

Open
wants to merge 13 commits into
base: next
Choose a base branch
from

Conversation

zardoy
Copy link
Owner

@zardoy zardoy commented Mar 10, 2025

PR Type

Enhancement


Description

  • Introduced video rendering capabilities in the WorldRendererThree class.

    • Added methods for adding, controlling, and destroying videos.
    • Implemented UV mapping and rotation for video textures.
  • Registered custom media channels for handling video operations.

    • Added support for media add, play, pause, seek, and destroy actions.
    • Defined packet structures for media-related channels.
  • Added a test utility for adding videos to the scene.


Changes walkthrough 📝

Relevant files
Enhancement
worldrendererThree.ts
Introduced video rendering capabilities in WorldRendererThree

renderer/viewer/lib/worldrendererThree.ts

  • Added VideoProperties interface for video configurations.
  • Implemented methods for adding, playing, seeking, and destroying
    videos.
  • Introduced UV mapping and rotation handling for video textures.
  • Added customVideos map to manage video elements and textures.
  • +143/-0 
    customChannels.ts
    Added media channel registration and test utility               

    src/customChannels.ts

  • Registered custom media channels for video operations.
  • Defined packet structures for media add, play, pause, seek, and
    destroy.
  • Added a test utility for adding videos to the scene.
  • Integrated video handling with WorldRendererThree.
  • +150/-0 

    Need help?
  • Type /help how to ... in the comments thread for any questions about Qodo Merge usage.
  • Check out the documentation for more information.
  • Summary by CodeRabbit

    • New Features
      • Introduced video support within 3D scenes with dynamic video addition and comprehensive playback controls (play, pause, seek, volume, and speed).
      • Added media channel integration that facilitates external commands to manage video content and testing.
      • Enhanced mouse and touch interactions with improved visual feedback for video-related actions.
      • Now includes a configuration option to toggle network-related channel interactions.
      • New interaction hints for video elements, prioritizing video interactions over entity actions.
      • Added a new property for handling remote content origin settings.

    Copy link

    codesandbox bot commented Mar 10, 2025

    Review or Edit in CodeSandbox

    Open the branch in Web EditorVS CodeInsiders

    Open Preview

    Copy link

    coderabbitai bot commented Mar 10, 2025

    Walkthrough

    The changes expand the 3D renderer to support video elements in the scene by introducing a new interface for media properties and multiple methods for video management in the renderer. A set of media channels is registered to control video addition, playback, seeking, volume, speed, and cleanup. Mouse and UI interaction handlers are updated to detect and process video interactions. Additionally, a configuration option for handling cross-origin content has been added.

    Changes

    File(s) Change Summary
    renderer/viewer/lib/worldrendererThree.ts Added MediaProperties interface, customMedia map, and methods for managing media: createErrorTexture, createBackgroundTexture, validateOrigin, addMedia, setVideoPlaying, setVideoSeeking, setVideoVolume, setVideoSpeed, and destroyMedia.
    src/customChannels.ts Introduced registerMediaChannels, MEDIA_INTERACTION_CHANNEL, and exported sendVideoInteraction along with new media channels (ADD_CHANNEL, PLAY_CHANNEL, PAUSE_CHANNEL, SEEK_CHANNEL, VOLUME_CHANNEL, SPEED_CHANNEL, DESTROY_CHANNEL) for handling video commands.
    src/mineflayer/plugins/mouse.ts Integrated video interaction in mouse event handling by importing and using sendVideoInteraction and videoCursorInteraction to trigger video-related actions before standard click handlers.
    src/optionsStorage.ts Added new property remoteContentNotSameOrigin to defaultOptions with a default value of false.
    src/react/GameInteractionOverlay.tsx, src/react/TouchInteractionHint.tsx Updated UI interaction logic by importing videoCursorInteraction and modifying conditions in pointer handlers so that video interactions can trigger attack actions or display a video interaction hint, prioritizing video elements over entity interactions.

    Sequence Diagram(s)

    sequenceDiagram
        participant Client as "User Client"
        participant Channels as "Media Channels Handler"
        participant Renderer as "WorldRendererThree"
        
        Client ->> Channels: Send ADD_CHANNEL command with video properties
        Channels ->> Renderer: Call destroyMedia(id) if media exists
        Channels ->> Renderer: Invoke addMedia(id, props)
        Renderer -->> Channels: Media element configured
    
    Loading
    sequenceDiagram
        participant Mouse as "Mouse Event Handler"
        participant Channels as "Video Interaction Handler"
        participant UI as "React UI Overlay"
        
        Mouse ->> Channels: Check videoCursorInteraction()
        alt Video Interaction Detected
            Mouse ->> Channels: Call sendVideoInteraction(id, x, y, leftClick?)
            UI ->> UI: Display "Interact with video" hint
        else No Video Interaction
            UI ->> UI: Evaluate cursor state for entity \n and show "Attack" hint if applicable
        end
    
    Loading

    Suggested labels

    Review effort 4/5

    Poem

    Oh, how I hop and code through the night,
    Videos now dance in a three-dimensional light.
    Media channels chime with each playful command,
    With clicks and hints, they work as planned.
    I nibble bugs and cheer with delight—
    A coding rabbit’s dream in digital flight! 🐇✨

    ✨ Finishing Touches
    • 📝 Generate Docstrings

    🪧 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>, please review it.
      • 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 gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
      • @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.
      • @coderabbitai help me debug CodeRabbit configuration file.

    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 using PR comments)

    • @coderabbitai pause to pause the reviews on a PR.
    • @coderabbitai resume to resume the paused reviews.
    • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
    • @coderabbitai full review to do a full review from scratch and review all the files again.
    • @coderabbitai summary to regenerate the summary of the PR.
    • @coderabbitai generate docstrings to generate docstrings for this PR.
    • @coderabbitai resolve resolve all the CodeRabbit review comments.
    • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
    • @coderabbitai help to get help.

    Other keywords and placeholders

    • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
    • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
    • Add @coderabbitai anywhere in the PR title to generate the title automatically.

    CodeRabbit Configuration 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.

    PR Reviewer Guide 🔍

    Here are some key observations to aid the review process:

    ⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
    🧪 No relevant tests
    🔒 Security concerns

    Cross-site scripting (XSS):
    The implementation allows loading videos from arbitrary sources specified in the media-add channel packets. This could potentially be exploited to load malicious content if the source URL is not properly validated. An attacker could send a specially crafted packet with a malicious source URL that executes JavaScript when loaded.

    ⚡ Recommended focus areas for review

    Memory Leak

    The video animation loop creates a new requestAnimationFrame callback on each video add, but there's no cleanup when videos are destroyed. This could cause memory leaks and performance degradation.

    const animate = () => {
      if (video.readyState === video.HAVE_ENOUGH_DATA) {
        videoTexture.needsUpdate = true
      }
      requestAnimationFrame(animate)
    }
    animate()
    Error Handling

    The media channel handlers don't have proper error handling for cases where the video source might be invalid or inaccessible, which could lead to silent failures.

    bot._client.on(ADD_CHANNEL as any, (data) => {
      const { id, x, y, z, width, height, rotation, source, loop } = data
      const worldRenderer = viewer.world as WorldRendererThree
    
      // Destroy existing video if it exists
      worldRenderer.destroyVideo(id)
    
      // Add new video
      worldRenderer.addVideo(id, {
        position: { x, y, z },
        size: { width, height },
        side: 'towards',
        src: source,
        rotation: rotation as 0 | 1 | 2 | 3,
        doubleSide: false
      })
    
      // Set loop state
      if (!loop) {
        const videoData = worldRenderer.customVideos.get(id)
        if (videoData) {
          videoData.video.loop = false
        }
      }
    })
    Exposed Test Function

    The addTestVideo function is exposed to the global window object, which might not be intended for production code and could pose security risks.

    window.addTestVideo = addTestVideo

    Copy link

    qodo-merge-pro-for-open-source bot commented Mar 10, 2025

    PR Code Suggestions ✨

    Explore these optional code suggestions:

    CategorySuggestion                                                                                                                                    Impact
    Possible issue
    Fix animation loop leak
    Suggestion Impact:The commit addressed the animation loop issue but in a different way. Instead of using requestAnimationFrame with cleanup, it changed the animation approach to use the mesh's onBeforeRender callback, which automatically runs only when the object is in the scene and doesn't need explicit cleanup.

    code diff:

         // Update texture in animation loop
    -    const animate = () => {
    +    mesh.onBeforeRender = () => {
           if (video.readyState === video.HAVE_ENOUGH_DATA) {
    +        if (material.map !== videoTexture) {
    +          material.map = videoTexture
    +          material.needsUpdate = true
    +        }
             videoTexture.needsUpdate = true
           }
    -      requestAnimationFrame(animate)
    -    }
    -    animate()
    +    }

    The animation loop creates a new requestAnimationFrame for each video, which
    will continue running even after the component is unmounted or the video is no
    longer needed, potentially causing memory leaks and performance issues.

    renderer/viewer/lib/worldrendererThree.ts [532-539]

    -// Update texture in animation loop
    +// Update texture in animation loop with cleanup capability
    +let animationFrameId: number;
     const animate = () => {
       if (video.readyState === video.HAVE_ENOUGH_DATA) {
         videoTexture.needsUpdate = true
       }
    -  requestAnimationFrame(animate)
    +  animationFrameId = requestAnimationFrame(animate)
     }
     animate()
     
    +// Store the animation frame ID for cleanup in destroyVideo
    +this.customVideos.set(id, {
    +  mesh,
    +  video,
    +  texture: videoTexture,
    +  updateUVMapping,
    +  animationFrameId
    +})
    +

    [Suggestion has been applied]

    Suggestion importance[1-10]: 9

    __

    Why: This suggestion addresses a critical memory leak issue where animation loops continue running indefinitely. By storing the animation frame ID and enabling proper cleanup, it prevents performance degradation and resource consumption over time.

    High
    Validate rotation values
    Suggestion Impact:The commit addressed the rotation validation issue, but in a different way. Instead of implementing the suggested normalization logic, the commit completely changed the rotation handling by hardcoding rotation to 0 and adding more properties like background and opacity.

    code diff:

    @@ -178,8 +190,11 @@
           size: { width, height },
           side: 'towards',
           src: source,
    -      rotation: rotation as 0 | 1 | 2 | 3,
    -      doubleSide: false
    +      // rotation: rotation as 0 | 1 | 2 | 3,
    +      rotation: 0,
    +      doubleSide: false,
    +      background,
    +      opacity: opacity / 100
         })

    The code doesn't validate the rotation value before casting it to the expected
    type. Since the protocol allows values 0-3 (and 3-6 for double side according to
    the comment), the code should properly validate and handle these values to
    prevent runtime errors.

    src/customChannels.ts [167-183]

     // Handle media add
     bot._client.on(ADD_CHANNEL as any, (data) => {
       const { id, x, y, z, width, height, rotation, source, loop } = data
       const worldRenderer = viewer.world as WorldRendererThree
     
       // Destroy existing video if it exists
       worldRenderer.destroyVideo(id)
     
    +  // Validate rotation (0-3) and check for double side (3-6)
    +  const normalizedRotation = rotation % 4 as 0 | 1 | 2 | 3
    +  const doubleSide = rotation >= 3 && rotation <= 6
    +
       // Add new video
       worldRenderer.addVideo(id, {
         position: { x, y, z },
         size: { width, height },
         side: 'towards',
         src: source,
    -    rotation: rotation as 0 | 1 | 2 | 3,
    -    doubleSide: false
    +    rotation: normalizedRotation,
    +    doubleSide
       })

    [Suggestion has been applied]

    Suggestion importance[1-10]: 8

    __

    Why: The suggestion correctly identifies a potential runtime error by properly validating rotation values and handling the doubleSide property. This prevents type errors and ensures the rotation values are correctly processed according to the protocol specification.

    Medium
    Add video error handling
    Suggestion Impact:The commit implemented error handling for video loading failures, but in a different way than suggested. Instead of adding an onerror event handler, it added a catch handler to the video.play() promise at line 91.

    code diff:

         video.play().catch(console.error)

    Add error handling for video loading failures. The current implementation
    doesn't handle cases where the video source is invalid or unavailable, which
    could lead to silent failures.

    renderer/viewer/lib/worldrendererThree.ts [489-494]

     // Create video element
     const video = document.createElement('video')
     video.src = props.src
     video.loop = true
     video.muted = true
     video.playsInline = true
    +video.onerror = (e) => {
    +  console.error(`Failed to load video ${props.src}:`, e)
    +}

    [Suggestion has been applied]

    Suggestion importance[1-10]: 7

    __

    Why: Adding error handling for video loading failures is important as it prevents silent failures when video sources are invalid or unavailable. This improves debugging and user experience by providing clear error messages.

    Medium
    • Update

    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: 0

    🧹 Nitpick comments (7)
    src/customChannels.ts (6)

    114-137: Validate unused fields and ensure they don’t cause confusion.
    Some fields in the addPacketStructure (e.g., _volume, _aspectRatioMode, _background, etc.) are not utilized within the handler. Consider removing them or using them as needed to reduce dead code.


    168-192: Potential race condition with default looping.
    When loop is false, the code sets video.loop = false after addVideo sets video.loop = true by default. Although eventually corrected, the initial frames might still see loop = true.

    Consider passing the loop setting directly into addVideo to avoid any momentary mismatch.

    -worldRenderer.addVideo(id, {
    +worldRenderer.addVideo(id, {
    +  loop,
       position: { x, y, z },
       ...

    225-229: Check “test-video” naming in production.
    The function’s name and ID are purely for testing. If intended for production, rename to avoid confusion.


    234-249: Consider making video source configurable.
    For broader testing, you might accept a dynamic media source instead of the hardcoded 'video1.mp4'.


    251-251: Exposing addTestVideo to global scope.
    If this is only for debugging, ensure it’s removed or guarded before release.


    253-260: Clean up commented-out code.
    If this functionality is deprecated or not needed, consider removing altogether to maintain clarity.

    renderer/viewer/lib/worldrendererThree.ts (1)

    477-486: Positional offset approach might be too rigid.
    Currently, calculateVideoPosition offsets only the Z coordinate. If additional rotations/axes are involved in the future, consider making this more generic.

    📜 Review details

    Configuration used: CodeRabbit UI
    Review profile: CHILL
    Plan: Pro

    📥 Commits

    Reviewing files that changed from the base of the PR and between a7e6f97 and 00e89ff.

    📒 Files selected for processing (2)
    • renderer/viewer/lib/worldrendererThree.ts (3 hunks)
    • src/customChannels.ts (3 hunks)
    ⏰ Context from checks skipped due to timeout of 90000ms (1)
    • GitHub Check: build-and-deploy
    🔇 Additional comments (19)
    src/customChannels.ts (11)

    2-3: Imports look fine.
    No obvious issues with importing THREE or WorldRendererThree.


    14-14: Good call registering media channels.
    This makes the media functionality available at bot creation.


    139-150: Control packet structure is well-defined.
    The basic "id" field is clear and minimal, which helps reduce complexity.


    152-158: Ensure negative seeks are handled or restricted.
    If negative or extreme values for "seconds" are sent, you might get unexpected playback behavior. Consider clamping or validating here.


    160-166: Channel registration is consistent.
    All channels are registered with the correct packet structures, enabling a straightforward media control flow.


    194-199: Playback toggles appear correct.
    Invoking setVideoPlaying is straightforward. Just bear in mind that mobile devices often require a user gesture for autoplay.


    201-206: Pause handler is correct.
    No concerns found here. The approach is consistent with the "play" handler.


    208-213: Seek handler is succinct.
    Just confirm no edge cases cause playback errors (e.g., seeking beyond video duration).


    215-220: Destroy handler seems robust.
    Removes the video from the scene and calls destroyVideo(), ensuring proper cleanup.


    222-223: Debug message is helpful.
    The log clarifies success, so this is good for initial diagnostics.


    230-233: Destroy old test video cleanly.
    This ensures no stale test-video instance remains in the scene.

    renderer/viewer/lib/worldrendererThree.ts (8)

    19-27: Interface appears well-structured.
    Clearly defines all essential properties for video rendering.


    41-46: Centralized video store is effective.
    The Map provides straightforward video tracking. Ensure you handle memory disposal promptly when a video is removed.


    488-503: Initialize video carefully.
    Hardcoding video.loop = true might conflict with the user’s loop intent. Also note that multiple videos each run requestAnimationFrame(animate), which could cause performance overhead if many videos are added.


    504-553: UV mapping support is flexible.
    The dynamic UV adjustment is a nice touch. If you anticipate transitions or animations for cropping, ensure speed or performance is handled gracefully.


    555-569: Storing video data in customVideos is good.
    The approach centralizes control and maintenance. The method returns the id consistently for confirmation.


    571-580: Simple and straightforward play/pause logic.
    No major concerns. Ensure to handle any browser policy restrictions requiring user gestures.


    582-587: No boundary checks for seeking.
    If the video is short and seconds is large, ensure you gracefully handle or clamp the value.


    589-602: Cleanup is thorough.
    This method disposes of texture, geometry, and material. Great job preventing memory leaks.

    @zardoy
    Copy link
    Owner Author

    zardoy commented Mar 10, 2025

    wdyt? @Phoenix616

    TODO fix rotation
    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: 2

    🧹 Nitpick comments (8)
    src/customChannels.ts (4)

    114-137: Media Add Channel structure requires optimization for future extensibility.

    The packet structure includes several parameters prefixed with underscore (_) that appear to be placeholder values. While this approach allows for future expansion, consider the following improvements:

    1. Group related parameters into structured objects
    2. Consider using versioned packet structures for better backward compatibility
    3. Document the purpose of underscore-prefixed parameters
    const addPacketStructure = [
      'container',
      [
        { name: 'id', type: ['pstring', { countType: 'i16' }] },
        { name: 'x', type: 'i32' },
        { name: 'y', type: 'i32' },
        { name: 'z', type: 'i32' },
        { name: 'width', type: 'f32' },
        { name: 'height', type: 'f32' },
        { name: 'rotation', type: 'u8' }, // 0-3 for 0°, 90°, 180°, 270° (3-6 is same but double side)
        { name: 'source', type: ['pstring', { countType: 'i16' }] },
        { name: 'loop', type: 'bool' },
    -   { name: '_volume', type: 'f32' }, // 0
    -   { name: '_aspectRatioMode', type: 'u8' }, // 0
    -   { name: '_background', type: 'u8' }, // 0
    -   { name: '_opacity', type: 'u8' }, // 1
    -   { name: '_cropXStart', type: 'f32' }, // 0
    -   { name: '_cropYStart', type: 'f32' }, // 0
    -   { name: '_cropXEnd', type: 'f32' }, // 0
    -   { name: '_cropYEnd', type: 'f32' }, // 0
    +   { name: 'options', type: ['pstring', { countType: 'i16' }] }, // JSON string for additional options
      ]
    ]

    195-200: Consider implementing response mechanism for media control operations.

    The current implementation doesn't provide any feedback on whether control operations (play, pause, seek, destroy) were successful. For robust client-server communication, consider adding a response or acknowledgment mechanism.

    A feedback mechanism could be implemented by adding a callback channel or using promises to handle success/failure states. This would allow the server to know if the operation succeeded.

    Also applies to: 202-207, 209-214, 216-221


    226-251: Test function exposed globally creates potential namespace pollution.

    The addTestVideo function is exposed directly to the window object, which could lead to namespace conflicts in a production environment. Consider a more isolated approach for testing functionality.

    const addTestVideo = (rotation = 0 as 0 | 1 | 2 | 3, scale = 1) => {
      // Function implementation
    }
    - window.addTestVideo = addTestVideo
    + // Only expose in development mode
    + if (process.env.NODE_ENV === 'development') {
    +   (window as any).addTestVideo = addTestVideo
    + }

    254-261: Remove commented-out code before merging.

    There's commented-out code for a registerMediaOpenChannel function that appears incomplete. Either complete and integrate this functionality or remove it from the codebase.

    Remove the commented-out incomplete code to maintain a clean codebase.

    renderer/viewer/lib/worldrendererThree.ts (4)

    477-500: Video positioning calculation needs validation logic.

    The calculateVideoPosition method handles rotation properly, but lacks validation for edge cases. Consider adding validation for input parameters and handling potential out-of-range situations.

    private calculateVideoPosition (position: { x: number, y: number, z: number }, side: 'towards' | 'away', rotation?: 0 | 1 | 2 | 3): { x: number, y: number, z: number } {
    +  // Validate inputs
    +  if (rotation !== undefined && (rotation < 0 || rotation > 3)) {
    +    console.warn('Invalid rotation value:', rotation, 'using default 0')
    +    rotation = 0
    +  }
    
      const offset = side === 'towards' ? 0.999 : 0.001
      // Position is bottom-left corner, so we need to add half the size to center it
      // Only apply towards/away offset to Z-axis, normalize X/Y with 0.5
      const baseX = Math.floor(position.x) + 0.5
      const baseY = Math.floor(position.y) + 0.5
      const baseZ = Math.floor(position.z) + (side === 'towards' ? 1 : 0) - offset
    
      // Adjust position based on rotation to maintain correct starting point
      if (rotation !== undefined) {
        // Rest of the code remains the same
      }
    
      return { x: baseX, y: baseY, z: baseZ }
    }

    581-591: Consider implementing an event system for video state changes.

    The current implementation lacks a way to notify other parts of the application about video state changes. Adding an event system would improve integration with UI components or other features.

    // Add to class definition
    + videoEvents = new EventTarget()
    
    // In addVideo method
    this.customVideos.set(id, {
      mesh: group,
      video,
      texture: videoTexture,
      updateUVMapping
    })
    
    + // Dispatch event
    + this.videoEvents.dispatchEvent(new CustomEvent('videoAdded', { detail: { id } }))
    
    return id

    593-602: Add return value to indicate success of video control operations.

    The setVideoPlaying method doesn't return any value to indicate success or failure. Consider adding a return value to help callers handle error cases.

    - setVideoPlaying (id: string, playing: boolean) {
    + setVideoPlaying (id: string, playing: boolean): boolean {
      const videoData = this.customVideos.get(id)
      if (videoData) {
        if (playing) {
    -     videoData.video.play().catch(console.error)
    +     return videoData.video.play()
    +       .then(() => true)
    +       .catch(err => {
    +         console.error(`Error playing video ${id}:`, err)
    +         return false
    +       })
        } else {
          videoData.video.pause()
    +     return true
        }
      }
    +  return false
    }

    604-609: Add range validation for seeking operations.

    The setVideoSeeking method doesn't validate if the requested time is within the video duration, which could lead to seeking to invalid positions.

    setVideoSeeking (id: string, seconds: number) {
      const videoData = this.customVideos.get(id)
      if (videoData) {
    +   // Validate time range
    +   const duration = videoData.video.duration
    +   if (isNaN(duration)) {
    +     console.warn(`Cannot seek video ${id} - duration unknown`)
    +     return
    +   }
    +   seconds = Math.max(0, Math.min(seconds, duration))
        videoData.video.currentTime = seconds
      }
    }
    📜 Review details

    Configuration used: CodeRabbit UI
    Review profile: CHILL
    Plan: Pro

    📥 Commits

    Reviewing files that changed from the base of the PR and between 00e89ff and 3f6753d.

    📒 Files selected for processing (2)
    • renderer/viewer/lib/worldrendererThree.ts (3 hunks)
    • src/customChannels.ts (3 hunks)
    ⏰ Context from checks skipped due to timeout of 90000ms (1)
    • GitHub Check: build-and-deploy
    🔇 Additional comments (6)
    src/customChannels.ts (2)

    2-3: Proper imports added for handling media functionality.

    The necessary import of THREE.js and the WorldRendererThree class has been added, which are required for the new video functionality.


    14-14: Registration of media channels properly hooked into initialization flow.

    The media channels registration function is correctly called during bot creation, consistent with the existing pattern used for block models channel registration.

    renderer/viewer/lib/worldrendererThree.ts (4)

    19-27: Well-defined interface for video properties.

    The VideoProperties interface provides a clear contract for video integration with appropriate optional properties. This allows for flexible video configuration while maintaining type safety.


    41-46: Customized video storage with proper typing.

    The customVideos Map is well-structured with appropriate types for storing video-related data including meshes, video elements, and textures. The inclusion of a function to update UV mapping provides a clean API for texture manipulation.


    563-580: UV mapping implementation is robust.

    The implementation of UV mapping for videos is well-designed, allowing for dynamic texture coordinate adjustment. This provides flexibility for features like video cropping and texture transforms.


    611-630: Thorough cleanup in destroyVideo method.

    The destroyVideo method properly cleans up all resources associated with a video, preventing memory leaks. This includes pausing the video, clearing its source, removing it from the scene, and disposing of textures and geometry.

    @zardoy
    Copy link
    Owner Author

    zardoy commented Mar 10, 2025

    /deploy

    Copy link

    Deployed to Vercel Preview: https://prismarine-1k1iy6rw2-zaro.vercel.app
    Playground
    Storybook

    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: 1

    ♻️ Duplicate comments (1)
    src/customChannels.ts (1)

    168-193: ⚠️ Potential issue

    Add error handling for media operations.

    The current implementation lacks error handling for potential issues like invalid URLs or playback failures. Media operations can fail due to various reasons including network issues, unsupported formats, or CORS restrictions.

    bot._client.on(ADD_CHANNEL as any, (data) => {
      const { id, x, y, z, width, height, rotation, source, loop } = data
      const worldRenderer = viewer.world as WorldRendererThree
    
      // Destroy existing video if it exists
      worldRenderer.destroyVideo(id)
    
    +  try {
        // Add new video
        worldRenderer.addVideo(id, {
          position: { x, y, z },
          size: { width, height },
          side: 'towards',
          src: source,
          rotation: rotation as 0 | 1 | 2 | 3,
          doubleSide: false
        })
    
        // Set loop state
        if (!loop) {
          const videoData = worldRenderer.customVideos.get(id)
          if (videoData) {
            videoData.video.loop = false
          }
        }
    +  } catch (error) {
    +    console.error(`Failed to add video ${id}:`, error)
    +  }
    })
    🧹 Nitpick comments (5)
    src/customChannels.ts (5)

    126-126: Clarify the rotation parameter comment.

    The comment states rotation values 3-6 are for double side, but in the implementation (line 183), doubleSide is explicitly set to false, and line 182 only casts to 0-3. This creates a potential confusion for users of this API.

    -      { name: 'rotation', type: 'u8' }, // 0-3 for 0°, 90°, 180°, 270° (3-6 is same but double side)
    +      { name: 'rotation', type: 'u8' }, // 0-3 for 0°, 90°, 180°, 270°

    129-136: Document the purpose of underscore-prefixed fields.

    These fields appear to be placeholders or future parameters that aren't currently being used in the implementation. Consider adding a more descriptive comment explaining their purpose or implementing them if they're intended to be used.


    171-171: Add type checking before casting to WorldRendererThree.

    The code assumes viewer.world is an instance of WorldRendererThree without verification. Add a check to prevent runtime errors if a different renderer is used.

    -const worldRenderer = viewer.world as WorldRendererThree
    +const worldRenderer = viewer.world
    +if (!(worldRenderer instanceof WorldRendererThree)) {
    +  console.error('Current world renderer does not support video functionality')
    +  return
    +}

    226-251: Improve the test video function implementation.

    The test function has several improvement opportunities:

    1. The video URL is hardcoded and might not always be available
    2. The function is exposed globally without any checks
    3. Missing error handling
    -const addTestVideo = (rotation = 0 as 0 | 1 | 2 | 3, scale = 1) => {
    +const addTestVideo = (rotation = 0 as 0 | 1 | 2 | 3, scale = 1, videoUrl = 'https://threejs.org/examples/textures/sintel.mp4') => {
      const block = window.cursorBlockRel()
      if (!block) return
      const { position: startPosition } = block
    
      const worldRenderer = viewer.world as WorldRendererThree
    +  if (!(worldRenderer instanceof WorldRendererThree)) {
    +    console.error('Current world renderer does not support video functionality')
    +    return
    +  }
    +  
    +  try {
        worldRenderer.destroyVideo('test-video')
    
        // Add video with proper positioning
        worldRenderer.addVideo('test-video', {
          position: {
            x: startPosition.x,
            y: startPosition.y,
            z: startPosition.z
          },
          size: {
            width: scale,
            height: scale
          },
          side: 'towards',
    -      src: 'https://threejs.org/examples/textures/sintel.mp4',
    +      src: videoUrl,
          rotation,
          doubleSide: true
        })
    +  } catch (error) {
    +    console.error('Failed to add test video:', error)
    +  }
    }
    -window.addTestVideo = addTestVideo
    +// Only expose in development environment
    +if (process.env.NODE_ENV !== 'production') {
    +  window.addTestVideo = addTestVideo
    +}

    114-224: Consider implementing a validation layer for received data.

    The current implementation assumes that all data received through channels is valid. Adding validation would improve robustness and help identify potential issues with the protocol or client implementation.

    You could implement a validation function for each packet type:

    function validateAddData(data: any): boolean {
      if (!data.id || typeof data.id !== 'string') return false;
      if (typeof data.x !== 'number' || typeof data.y !== 'number' || typeof data.z !== 'number') return false;
      if (typeof data.width !== 'number' || typeof data.height !== 'number') return false;
      if (typeof data.rotation !== 'number' || data.rotation < 0 || data.rotation > 3) return false;
      if (!data.source || typeof data.source !== 'string') return false;
      return true;
    }
    
    // Then use in handler:
    bot._client.on(ADD_CHANNEL as any, (data) => {
      if (!validateAddData(data)) {
        console.error('Invalid data received for media add:', data);
        return;
      }
      
      // Rest of handler code...
    });
    📜 Review details

    Configuration used: CodeRabbit UI
    Review profile: CHILL
    Plan: Pro

    📥 Commits

    Reviewing files that changed from the base of the PR and between 3f6753d and 3d4b926.

    📒 Files selected for processing (1)
    • src/customChannels.ts (3 hunks)
    ⏰ Context from checks skipped due to timeout of 90000ms (1)
    • GitHub Check: build-and-deploy
    🔇 Additional comments (2)
    src/customChannels.ts (2)

    2-3: LGTM: Imports are appropriate for the new functionality.

    The THREE.js library and WorldRendererThree class are correctly imported to support the new video rendering capabilities.


    14-14: LGTM: Media channels registration added to bot initialization.

    The new function is properly called within the existing bot initialization flow.

    @zardoy
    Copy link
    Owner Author

    zardoy commented Mar 10, 2025

    /deploy

    Copy link

    Deployed to Vercel Preview: https://prismarine-jze2rqbgz-zaro.vercel.app
    Playground
    Storybook

    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: 1

    🧹 Nitpick comments (1)
    renderer/viewer/lib/worldrendererThree.ts (1)

    502-592: Consolidate position logic to avoid code duplication.

    The code for computing block center and offset in lines 534-539 mirrors what you introduced in calculateVideoPosition(). Unifying these computations would help maintain consistency, reduce mistakes, and ease future changes.

    Here’s a minimal diff example that replaces the inline calculation with calculateVideoPosition():

    --- Before
    +-- After
    @@ -533,14 +477,7 @@
     const group = new THREE.Group()
     group.add(mesh)
    
    -// Calculate base position (center of block)
    -const baseX = Math.floor(props.position.x) + 0.5
    -const baseY = Math.floor(props.position.y) + 0.5
    -const baseZ = Math.floor(props.position.z) + (props.side === 'towards' ? 1 : 0) - (props.side === 'towards' ? 0.999 : 0.001)
    -
    -group.position.set(baseX, baseY, baseZ)
    -
    -// ...
    +const calculated = this.calculateVideoPosition(props.position, props.side, props.rotation)
    +group.position.set(calculated.x, calculated.y, calculated.z)
    
     // Then remove the duplicated rotation code if it's handled within calculateVideoPosition or unify the approach
    📜 Review details

    Configuration used: CodeRabbit UI
    Review profile: CHILL
    Plan: Pro

    📥 Commits

    Reviewing files that changed from the base of the PR and between 3d4b926 and 29befe7.

    📒 Files selected for processing (1)
    • renderer/viewer/lib/worldrendererThree.ts (3 hunks)
    ⏰ Context from checks skipped due to timeout of 90000ms (1)
    • GitHub Check: build-and-deploy
    🔇 Additional comments (4)
    renderer/viewer/lib/worldrendererThree.ts (4)

    19-27: Well-defined interface.

    These properties comprehensively capture the requirements for video rendering. Nice work ensuring flexibility with optional fields like rotation, doubleSide, and uvMapping.


    41-46: Straightforward map approach.

    Storing all video resources in a centralized Map is clear and maintainable, facilitating easy retrieval and cleanup.


    477-500: Potentially unused helper function.

    calculateVideoPosition doesn’t appear to be called in this file, and its logic is separately re-implemented in addVideo(). To keep the codebase lean and consistent, either remove it or integrate it into addVideo() to avoid duplication.

    If it’s intentionally reserved for future usage, please confirm; otherwise, consider removing it.


    555-561: Performance concern with per-video animation loops.

    Each video spawns its own requestAnimationFrame loop, which can degrade performance when handling multiple videos. As previously noted in past reviews, consider consolidating these updates in a single animation loop to optimize performance.

    @Phoenix616
    Copy link

    wdyt? @Phoenix616

    Looks good to me. How smooth is the video? Just like on a normal website or is there some limitation on the frame rate? (besides the actual client renderer FPS of course)

    @zardoy
    Copy link
    Owner Author

    zardoy commented Mar 11, 2025

    Looks good to me. How smooth is the video? Just like on a normal website or is there some limitation on the frame rate? (besides the actual client renderer FPS of course)

    This is a video texture. It's just a simple image texture that is updated on every render call so obviously video can have any framerate (but since its web videos with 120+ fps might take more time to load over network)

    @zardoy
    Copy link
    Owner Author

    zardoy commented Mar 11, 2025

    https://github.com/user-attachments/assets/ec24fd4f-96d0-4973-9a26-1454b006daac

    test_video.mp4

    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: 1

    🧹 Nitpick comments (5)
    src/customChannels.ts (2)

    116-173: Good structured channel registration, consider minimal validation.
    All relevant packet structures are neatly described and registered. You could add minimal validation (e.g. numeric range checks) to guard against malformed data.

    + // Example: limit 'width', 'height' to sane ranges 
    + if (data.width < 0 || data.height < 0) {
    +   throw new Error("Invalid media dimensions")
    + }

    310-344: Useful test function, consider removing in production.
    addTestVideo is helpful for debugging, but ensure it’s gated or removed in production to avoid exposing test data.

    renderer/viewer/lib/worldrendererThree.ts (3)

    504-553: Use consistent text styling for error and background textures.
    The code is good for fallback visuals. For a more uniform look, ensure the text font size and opacity match your design guidelines across all fallback textures.


    555-665: Captures errors gracefully, but watch for autoplay restrictions.
    The video.play().catch(...) block is correct for logging errors. Note that some browsers block autoplay. Provide a fallback or user action for enabling playback if necessary.

    I can provide code to handle user gestures if desired; would you like me to open an issue for that?


    695-746: Lifecycle management is good, consider unsubscribing or removing all references.
    destroyVideo removes the mesh, texture, and video, but also ensure no leftover references in the broader application, e.g., external event listeners.

    + // Example cleanup in destroyVideo
    + videoData.video.removeAttribute('src')
    + videoData.video.load()
    📜 Review details

    Configuration used: CodeRabbit UI
    Review profile: CHILL
    Plan: Pro

    📥 Commits

    Reviewing files that changed from the base of the PR and between 29befe7 and 2c10316.

    📒 Files selected for processing (7)
    • renderer/viewer/lib/worldrendererThree.ts (3 hunks)
    • src/customChannels.ts (3 hunks)
    • src/index.ts (1 hunks)
    • src/mineflayer/plugins/mouse.ts (2 hunks)
    • src/optionsStorage.ts (1 hunks)
    • src/react/GameInteractionOverlay.tsx (2 hunks)
    • src/react/TouchInteractionHint.tsx (2 hunks)
    ⏰ Context from checks skipped due to timeout of 90000ms (1)
    • GitHub Check: build-and-deploy
    🔇 Additional comments (17)
    src/optionsStorage.ts (1)

    62-62: Good addition for feature configuration

    The new networkRelatedCustomChannels property provides a way to toggle network-related custom channel functionality, which aligns well with the video rendering capabilities being introduced in this PR.

    src/mineflayer/plugins/mouse.ts (2)

    25-25: Appropriate imports for video interaction functionality

    The imports for sendVideoInteraction and videoCursorInteraction are necessary for the new video interaction features being implemented.


    184-188: Good prioritization of video interactions

    This code correctly checks for video interactions first and handles them appropriately before falling back to standard mouse click handling. This prioritization ensures videos are properly interactive in the 3D scene.

    src/react/GameInteractionOverlay.tsx (2)

    8-8: Appropriate import for video cursor interaction

    The import of videoCursorInteraction is necessary for the extended functionality.


    157-157: Good enhancement of attack condition

    The condition for gonnaAttack has been correctly extended to include video interactions, ensuring that both entities and videos can be interacted with using the same input mechanism.

    src/react/TouchInteractionHint.tsx (2)

    5-5: Necessary import for video cursor interaction

    This import enables the touch interaction hint system to recognize video elements.


    18-29: Well-structured UI hint logic for video interactions

    The code has been restructured in a clear and logical way to:

    1. First check for video interactions
    2. Show an appropriate "Interact with video" hint when needed
    3. Fall back to entity-based hints if no video is being interacted with

    This change maintains a consistent user experience by providing contextual hints for all interactive elements.

    src/index.ts (1)

    897-898: Confirm correctness of the new subscribe usage.
    Switching from watchValue to subscribe appears valid, and you cleanly handle unsubscription right after fsReady and appConfig are set. This prevents potential leaks.

    src/customChannels.ts (6)

    14-16: Conditional registration check seems fine.
    The logic ensures media channels are only registered if options.networkRelatedCustomChannels is true, preventing unnecessary overhead. No concerns here.


    175-208: Ensure robust error handling for unsupported images.
    Rejecting image files is clear. However, adding a try-catch block to guard against other unexpected failures (e.g., invalid rotation or network errors) is beneficial.


    209-249: Add error handling for media control operations.
    As in prior suggestions, wrapping each video operation in a try-catch block helps handle unexpected failures gracefully.


    275-307: Raycasting logic is efficient, but watch out for camera or mesh changes.
    This center-screen approach works, yet if you expand to multiple cameras or off-center interactions, you'll need to adapt.


    253-273:

    ❓ Verification inconclusive

    Confirm interaction channel usage.
    The MEDIA_INTERACTION_CHANNEL and sendVideoInteraction method look correct, but re-check boundary flows: e.g., what if id doesn't exist in customVideos.


    🏁 Script executed:

    #!/bin/bash
    # Verify calls to sendVideoInteraction always reference existing video IDs
    rg -A 5 "sendVideoInteraction"

    Length of output: 1273


    Action Required: Validate Video ID Usage in Interaction Channel

    The registration of the interaction channel and the sendVideoInteraction method appears correct. However, please verify that every invocation of sendVideoInteraction uses a valid video ID from customVideos. For example, in src/mineflayer/plugins/mouse.ts the function is invoked with videoInteraction.id without any safeguard confirming that this ID exists in customVideos. Consider adding explicit validation or boundary checks to handle cases where an invalid or missing ID might be passed.


    2-3:

    ✅ Verification successful

    Validate imports for concurrency.
    You're importing THREE and WorldRendererThree at the top-level scope. Ensure that any references to viewer.world or bot throughout the file are always ready to consume these imports safely.

    Please confirm you never call worldRenderer or viewer.world before they are actually initialized by verifying all references:


    🏁 Script executed:

    #!/bin/bash
    # Search for references to `viewer.world` or `worldRenderer` before custom channels registration.
    rg -A 5 "viewer.world|worldRenderer"

    Length of output: 49974


    Concurrency Safety Verified

    After a comprehensive review of the codebase—especially the callbacks and event handlers in src/customChannels.ts—all references to viewer.world (and its alias worldRenderer) occur only within function bodies or event listener callbacks that are invoked after the viewer is fully initialized. There are no instances where these references are called at the module level or before the proper initialization routines are complete. The usage is consistent with the safe patterns used throughout other parts of the repository.

    renderer/viewer/lib/worldrendererThree.ts (3)

    19-29: Interface design for VideoProperties looks clear.
    Properties are well-documented. The optional uvMapping is a nice touch for custom cropping. No issues found.


    479-502: Check correctness of positional offset logic.
    Your coordinate adjustments for side and rotation look sensible but confirm with in-game testing that the final alignment meets the intended orientation.


    668-671: Position scaling method is short and clear.
    This helper ensures consistent application of (width, height, depth). No issues found.

    @zardoy
    Copy link
    Owner Author

    zardoy commented Mar 12, 2025

    Have no idea how to fix rotation and scale, uv (to preserve aspect ratio) and opacity

    @zardoy
    Copy link
    Owner Author

    zardoy commented Mar 12, 2025

    /deploy

    Copy link

    Deployed to Vercel Preview: https://prismarine-cxas0s3dj-zaro.vercel.app
    Playground
    Storybook

    @zardoy
    Copy link
    Owner Author

    zardoy commented Mar 12, 2025

    image

    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: 2

    ♻️ Duplicate comments (7)
    src/customChannels.ts (7)

    175-207: ⚠️ Potential issue

    Add error handling for media add operations and fix rotation handling.

    The current implementation throws an error when image files are detected but doesn't catch it, which could crash the application. Also, the rotation parameter is ignored and hardcoded to 0, contradicting the comment in the packet structure.

    bot._client.on(ADD_CHANNEL as any, (data) => {
      const { id, x, y, z, width, height, rotation, source, loop, background, opacity } = data
    
    +  try {
        if (source.endsWith('.png') || source.endsWith('.jpg') || source.endsWith('.jpeg')) {
    -      throw new Error('Image files are not supported yet, please use Minecraft maps instead')
    +      console.error('Image files are not supported yet, please use Minecraft maps instead')
    +      return;
        }
    
        const worldRenderer = viewer.world as WorldRendererThree
    
        // Destroy existing video if it exists
        worldRenderer.destroyVideo(id)
    
        // Add new video
        worldRenderer.addVideo(id, {
          position: { x, y, z },
          size: { width, height },
          side: 'towards',
          src: source,
    -      // rotation: rotation as 0 | 1 | 2 | 3,
    -      rotation: 0,
    +      rotation: rotation as 0 | 1 | 2 | 3,
          doubleSide: false,
          background,
          opacity: opacity / 100
        })
    
        // Set loop state
        if (!loop) {
          const videoData = worldRenderer.customVideos.get(id)
          if (videoData) {
            videoData.video.loop = false
          }
        }
    +  } catch (error) {
    +    console.error(`Failed to add video ${id}:`, error)
    +  }
    })

    209-214: ⚠️ Potential issue

    Add error handling for play operation.

    Similar to the Add handler, this operation lacks error handling. Wrap the operation in a try-catch block to gracefully handle failures.

    bot._client.on(PLAY_CHANNEL as any, (data) => {
      const { id } = data
      const worldRenderer = viewer.world as WorldRendererThree
    +  try {
        worldRenderer.setVideoPlaying(id, true)
    +  } catch (error) {
    +    console.error(`Failed to play video ${id}:`, error)
    +  }
    })

    217-221: ⚠️ Potential issue

    Add error handling for pause operation.

    Add try-catch block to handle potential failures when pausing a video.

    bot._client.on(PAUSE_CHANNEL as any, (data) => {
      const { id } = data
      const worldRenderer = viewer.world as WorldRendererThree
    +  try {
        worldRenderer.setVideoPlaying(id, false)
    +  } catch (error) {
    +    console.error(`Failed to pause video ${id}:`, error)
    +  }
    })

    224-228: ⚠️ Potential issue

    Add error handling for seek operation and update parameter name.

    Add try-catch block and update the parameter name to match the packet structure.

    bot._client.on(SEEK_CHANNEL as any, (data) => {
    -  const { id, seconds } = data
    +  const { id, value } = data
      const worldRenderer = viewer.world as WorldRendererThree
    +  try {
    -    worldRenderer.setVideoSeeking(id, seconds)
    +    worldRenderer.setVideoSeeking(id, value)
    +  } catch (error) {
    +    console.error(`Failed to seek video ${id}:`, error)
    +  }
    })

    231-235: ⚠️ Potential issue

    Add error handling for destroy operation.

    Add try-catch block to handle potential failures when destroying a video.

    bot._client.on(DESTROY_CHANNEL as any, (data) => {
      const { id } = data
      const worldRenderer = viewer.world as WorldRendererThree
    +  try {
        worldRenderer.destroyVideo(id)
    +  } catch (error) {
    +    console.error(`Failed to destroy video ${id}:`, error)
    +  }
    })

    238-242: ⚠️ Potential issue

    Add error handling for volume operation and update parameter name.

    Add try-catch block and update the parameter name to match the packet structure.

    bot._client.on(VOLUME_CHANNEL as any, (data) => {
    -  const { id, volume } = data
    +  const { id, value } = data
      const worldRenderer = viewer.world as WorldRendererThree
    +  try {
    -    worldRenderer.setVideoVolume(id, volume)
    +    worldRenderer.setVideoVolume(id, value)
    +  } catch (error) {
    +    console.error(`Failed to set volume for video ${id}:`, error)
    +  }
    })

    245-249: ⚠️ Potential issue

    Add error handling for speed operation and update parameter name.

    Add try-catch block and update the parameter name to match the packet structure.

    bot._client.on(SPEED_CHANNEL as any, (data) => {
    -  const { id, speed } = data
    +  const { id, value } = data
      const worldRenderer = viewer.world as WorldRendererThree
    +  try {
    -    worldRenderer.setVideoSpeed(id, speed)
    +    worldRenderer.setVideoSpeed(id, value)
    +  } catch (error) {
    +    console.error(`Failed to set speed for video ${id}:`, error)
    +  }
    })
    🧹 Nitpick comments (3)
    src/customChannels.ts (3)

    157-163: Improve parameter naming in setNumberPacketStructure.

    The parameter name seconds is used for both time-related operations (seek) and non-time-related operations (volume, speed). A more generic name like value would be more appropriate.

    const setNumberPacketStructure = [
      'container',
      [
        { name: 'id', type: ['pstring', { countType: 'i16' }] },
    -    { name: 'seconds', type: 'f32' }
    +    { name: 'value', type: 'f32' }
      ]
    ]

    269-273: Add validation to sendVideoInteraction function.

    The function should validate input parameters to prevent potential errors.

    export const sendVideoInteraction = (id: string, x: number, y: number, isRightClick: boolean) => {
    +  if (!id) {
    +    console.error('Video ID is required for interaction')
    +    return
    +  }
    +  
    +  if (isNaN(x) || isNaN(y)) {
    +    console.error('Invalid coordinates for video interaction')
    +    return
    +  }
      
      bot._client.writeChannel(MEDIA_INTERACTION_CHANNEL, { id, x, y, isRightClick })
    }

    275-307: Add error handling to videoCursorInteraction function.

    The function should handle potential errors that might occur during raycasting or when accessing objects that might not be fully initialized.

    export const videoCursorInteraction = () => {
    +  try {
        const worldRenderer = viewer.world as WorldRendererThree
    +    if (!worldRenderer || !worldRenderer.camera) {
    +      return null
    +    }
        
        const { camera } = worldRenderer
        const raycaster = new THREE.Raycaster()
    
        // Get mouse position at center of screen
        const mouse = new THREE.Vector2(0, 0)
    
        // Update the raycaster
        raycaster.setFromCamera(mouse, camera)
    
        // Check intersection with all video meshes
        for (const [id, videoData] of worldRenderer.customVideos.entries()) {
          // Get the actual mesh (first child of the group)
          const mesh = videoData.mesh.children[0] as THREE.Mesh
          if (!mesh) continue
    
          const intersects = raycaster.intersectObject(mesh, false)
          if (intersects.length > 0) {
            const intersection = intersects[0]
            const { uv } = intersection
            if (!uv) return null
    
            return {
              id,
              x: uv.x,
              y: uv.y
            }
          }
        }
    
        return null
    +  } catch (error) {
    +    console.error('Error in videoCursorInteraction:', error)
    +    return null
    +  }
    }
    📜 Review details

    Configuration used: CodeRabbit UI
    Review profile: CHILL
    Plan: Pro

    📥 Commits

    Reviewing files that changed from the base of the PR and between 2c10316 and facf760.

    📒 Files selected for processing (1)
    • src/customChannels.ts (3 hunks)
    ⏰ Context from checks skipped due to timeout of 90000ms (1)
    • GitHub Check: build-and-deploy
    🔇 Additional comments (2)
    src/customChannels.ts (2)

    2-3: Appropriate imports for THREE.js integration.

    Good addition of necessary imports for the new video functionality. The THREE.js library is essential for the 3D rendering capabilities.


    14-16: Good conditional registration of media channels.

    Wrapping the media channel registration inside the options.networkRelatedCustomChannels check is a good practice, as it provides a way to disable this feature when not needed.

    @zardoy
    Copy link
    Owner Author

    zardoy commented Mar 18, 2025

    /deploy

    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: 1

    ♻️ Duplicate comments (4)
    src/customChannels.ts (4)

    304-336: 🛠️ Refactor suggestion

    Add error handling to addTestVideo and address TODO comment.

    The function contains a TODO comment about broken UV mapping that should be addressed or documented better. Additionally, add error handling to gracefully manage failures.

    const addTestVideo = (rotation = 0 as 0 | 1 | 2 | 3, scale = 1, isImage = false) => {
    +  try {
        const block = window.cursorBlockRel()
        if (!block) return
        const { position: startPosition } = block
    
        const worldRenderer = viewer.world as WorldRendererThree
    
        // Add video with proper positioning
        worldRenderer.addMedia('test-video', {
          position: {
            x: startPosition.x,
            y: startPosition.y + 1,
            z: startPosition.z
          },
          size: {
            width: scale,
            height: scale
          },
          src: isImage ? 'https://bucket.mcraft.fun/test_image.png' : 'https://bucket.mcraft.fun/test_video.mp4',
          rotation,
          // doubleSide: true,
          background: 0x00_00_00, // Black color
          // TODO broken
          // uvMapping: {
          //   startU: 0,
          //   endU: 1,
          //   startV: 0,
          //   endV: 1
          // },
          opacity: 1,
          allowOrigins: true,
        })
    +  } catch (error) {
    +    console.error('Error in addTestVideo:', error)
    +  }
    }

    Also, consider creating a GitHub issue to track the broken UV mapping functionality mentioned in the TODO comment.


    119-137: ⚠️ Potential issue

    Fix field naming inconsistency in packet structure.

    There's an inconsistency between the field names in the packet structure (prefixed with underscore) and their usage in the handler (without underscore). For example, _background is defined but accessed as background in line 189.

    -      { name: '_volume', type: 'f32' }, // 0
    +      { name: 'volume', type: 'f32' }, // 0
    -      { name: '_aspectRatioMode', type: 'u8' }, // 0
    +      { name: 'aspectRatioMode', type: 'u8' }, // 0
    -      { name: '_background', type: 'u8' }, // 0
    +      { name: 'background', type: 'u8' }, // 0
    -      { name: '_opacity', type: 'u8' }, // 1
    +      { name: 'opacity', type: 'u8' }, // 1
    -      { name: '_cropXStart', type: 'f32' }, // 0
    +      { name: 'cropXStart', type: 'f32' }, // 0
    -      { name: '_cropYStart', type: 'f32' }, // 0
    +      { name: 'cropYStart', type: 'f32' }, // 0
    -      { name: '_cropXEnd', type: 'f32' }, // 0
    +      { name: 'cropXEnd', type: 'f32' }, // 0
    -      { name: '_cropYEnd', type: 'f32' }, // 0
    +      { name: 'cropYEnd', type: 'f32' }, // 0

    203-243: 🛠️ Refactor suggestion

    Add error handling for media control operations.

    Similar to the Add handler, these control operations lack error handling. Wrap the operations in try-catch blocks to gracefully handle failures.

    // Example for one handler (apply similar pattern to all):
    bot._client.on(PLAY_CHANNEL as any, (data) => {
      const { id } = data
      const worldRenderer = viewer.world as WorldRendererThree
    +  try {
        worldRenderer.setVideoPlaying(id, true)
    +  } catch (error) {
    +    console.error(`Failed to play video ${id}:`, error)
    +  }
    })

    173-201: 🛠️ Refactor suggestion

    Add error handling for media operations.

    The current implementation lacks error handling for potential issues like invalid URLs or playback failures. Media operations can fail due to various reasons including network issues, unsupported formats, or CORS restrictions.

    bot._client.on(ADD_CHANNEL as any, (data) => {
      const { id, x, y, z, width, height, rotation, source, loop, background, opacity } = data
    
      const worldRenderer = viewer.world as WorldRendererThree
    
      // Destroy existing video if it exists
      worldRenderer.destroyMedia(id)
    
    +  try {
        // Add new video
        worldRenderer.addMedia(id, {
          position: { x, y, z },
          size: { width, height },
          // side: 'towards',
          src: source,
          rotation: rotation as 0 | 1 | 2 | 3,
          doubleSide: false,
          background,
          opacity: opacity / 100,
          allowOrigins: options.remoteContentNotSameOrigin === false ? [getCurrentTopDomain()] : options.remoteContentNotSameOrigin
        })
    
        // Set loop state
        if (!loop) {
          const videoData = worldRenderer.customMedia.get(id)
          if (videoData?.video) {
            videoData.video.loop = false
          }
        }
    +  } catch (error) {
    +    console.error(`Failed to add media ${id}:`, error)
    +  }
    })
    🧹 Nitpick comments (6)
    src/customChannels.ts (1)

    260-260: Add channel registration status to logs.

    Good logging practice includes listing all registered channels in debug output. Consider enhancing the log message to include all registered channel names.

    -  console.debug('Registered media channels')
    +  console.debug(`Registered media channels: ${ADD_CHANNEL}, ${PLAY_CHANNEL}, ${PAUSE_CHANNEL}, ${SEEK_CHANNEL}, ${VOLUME_CHANNEL}, ${SPEED_CHANNEL}, ${DESTROY_CHANNEL}, ${MEDIA_INTERACTION_CHANNEL}`)
    renderer/viewer/lib/worldrendererThree.ts (5)

    562-674: Performance concern with individual animation loops.

    Each video gets its own onBeforeRender callback, which can lead to performance issues when multiple videos are present. Consider consolidating these into a single animation loop.

    addMedia (id: string, props: MediaProperties) {
      // Create video element and other setup code...
    
      if (video) {
    -    // Update texture in animation loop
    -    mesh.onBeforeRender = () => {
    -      if (video.readyState === video.HAVE_ENOUGH_DATA) {
    -        if (material.map !== texture) {
    -          material.map = texture
    -          material.needsUpdate = true
    -        }
    -        texture.needsUpdate = true
    -      }
    -    }
    +    // Add to tracking set for centralized animation
    +    this._videosToUpdate.add({
    +      id,
    +      update: () => {
    +        if (video.readyState === video.HAVE_ENOUGH_DATA) {
    +          if (material.map !== texture) {
    +            material.map = texture
    +            material.needsUpdate = true
    +          }
    +          texture.needsUpdate = true
    +        }
    +      }
    +    })
      }
    
      // Rest of the function...
    }
    
    + // Add these to the class:
    + private _videosToUpdate = new Set<{id: string, update: () => void}>()
    + private _videoUpdateLoop = () => {
    +   // Update all video textures that need updating
    +   for (const item of this._videosToUpdate) {
    +     item.update()
    +   }
    +   requestAnimationFrame(this._videoUpdateLoop)
    + }
    + 
    + // Initialize the loop in the constructor
    + constructor(...) {
    +   // existing code
    +   this._videoUpdateLoop()
    + }

    676-685: Add timeout and error handling to video playback control.

    When setting video playback state, add timeout handling to prevent hanging on slow network conditions.

    setVideoPlaying (id: string, playing: boolean) {
      const videoData = this.customMedia.get(id)
      if (videoData?.video) {
        if (playing) {
    -      videoData.video.play().catch(console.error)
    +      const playPromise = videoData.video.play()
    +      if (playPromise !== undefined) {
    +        const timeoutPromise = new Promise((_, reject) => {
    +          setTimeout(() => reject(new Error('Video play timeout')), 5000)
    +        })
    +        
    +        Promise.race([playPromise, timeoutPromise])
    +          .catch(error => {
    +            console.error(`Failed to play video ${id}:`, error)
    +          })
    +      }
        } else {
          videoData.video.pause()
        }
      }
    }

    743-848: Simplify the debug visualization in positionMeshExact.

    The positioning logic is solid, but the debug visualization with multiple markers should be conditionally enabled based on a debug flag to avoid cluttering the scene in production.

    positionMeshExact (
      mesh: THREE.Mesh,
      rotation: number,
      startPosition: { x: number, y: number, z: number },
      width: number,
      height: number,
      depth = 1
    ) {
      // ... existing positioning code ...
    
      // Create a group to hold our mesh and markers
      const debugGroup = new THREE.Group()
      debugGroup.add(mesh)
    
    +  // Only add debug markers when in debug mode
    +  const showDebugMarkers = false; // Change to a class property or config option
    +  if (showDebugMarkers) {
        // Add a marker at the starting position (should be exactly at pos)
        const startMarker = new THREE.Mesh(
          new THREE.BoxGeometry(0.1, 0.1, 0.1),
          new THREE.MeshBasicMaterial({ color: 0xff_00_00 })
        )
        startMarker.position.copy(new THREE.Vector3(startPosition.x, startPosition.y, startPosition.z))
        debugGroup.add(startMarker)
    
        // ... other debug markers and visualization ...
    +  }
    
      return {
        mesh,
        debugGroup
      }
    }

    849-859: Remove or refactor unused test function.

    The createTestCanvasTexture function appears to be used only for testing. Consider removing it or refactoring it to be more generic if needed for production.

    -createTestCanvasTexture () {
    +// Removed or moved to a testing utility file

    864-887: Make addTestMeshExact a testing utility function.

    This function is purely for testing/debugging purposes. Consider moving it to a separate testing utilities file or make it conditionally available only in development mode.

    -addTestMeshExact (rotationNum: number) {
    +// Removed or moved to a testing utility file
    📜 Review details

    Configuration used: CodeRabbit UI
    Review profile: CHILL
    Plan: Pro

    📥 Commits

    Reviewing files that changed from the base of the PR and between 60aaa7c and 042f0c9.

    📒 Files selected for processing (3)
    • renderer/viewer/lib/worldrendererThree.ts (3 hunks)
    • src/customChannels.ts (3 hunks)
    • src/optionsStorage.ts (1 hunks)
    🚧 Files skipped from review as they are similar to previous changes (1)
    • src/optionsStorage.ts
    🔇 Additional comments (10)
    src/customChannels.ts (3)

    114-144: Good channel organization and naming.

    The media channels are well-organized with clear, descriptive names that follow a consistent pattern. The separation into distinct channels for different operations (add, play, pause, seek, etc.) follows good design principles.


    269-301: Well-implemented cursor interaction detection.

    The videoCursorInteraction function efficiently detects interactions with video meshes using raycasting. This is a clean implementation that properly handles the 3D to 2D mapping via UV coordinates.


    339-358: Well-implemented domain extraction logic.

    The getCurrentTopDomain function properly handles various domain formats, including special cases like country-specific domains (co.uk, com.br, etc.). This ensures proper origin security checks.

    renderer/viewer/lib/worldrendererThree.ts (7)

    19-29: Well-structured MediaProperties interface.

    The interface is comprehensive and includes all necessary properties for media handling, with appropriate typing and documentation. The optional properties are correctly defined with question marks.


    43-48: Good data structure for managing media elements.

    Using a Map for storing custom media elements with a well-defined value type is a good approach. It provides efficient lookup by ID while maintaining all necessary properties and methods for each media element.


    479-502: Good position calculation logic with rotation support.

    The calculateVideoPosition method handles different rotation cases properly, ensuring correct positioning of media elements in the 3D space regardless of their rotation angle.


    504-531: Well-implemented error texture for fallback scenarios.

    Creating a visual indicator when media fails to load is a good UX practice. The error texture implementation is thorough with proper scaling and text rendering.


    533-553: Efficient background texture implementation.

    Using a 1x1 canvas for solid color backgrounds is memory-efficient. The color conversion from hex to RGBA components is also well-implemented.


    647-664: Well-implemented UV mapping configuration.

    The UV mapping implementation allows for fine-grained control over texture coordinates, enabling features like cropping and custom aspect ratios. The update function is well-designed to modify all UV coordinates properly.


    708-729: Thorough resource cleanup in destroyMedia.

    The destroyMedia method properly handles cleanup of all resources including video elements, meshes, textures, and geometries. This is important to prevent memory leaks when videos are frequently added and removed.

    Comment on lines +627 to +644
    if (video) {
    // Start playing the video
    video.play().catch(err => {
    console.error('Failed to play video:', err)
    handleError()
    })

    // Update texture in animation loop
    mesh.onBeforeRender = () => {
    if (video.readyState === video.HAVE_ENOUGH_DATA) {
    if (material.map !== texture) {
    material.map = texture
    material.needsUpdate = true
    }
    texture.needsUpdate = true
    }
    }
    }
    Copy link

    @coderabbitai coderabbitai bot Mar 18, 2025

    Choose a reason for hiding this comment

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

    🛠️ Refactor suggestion

    Add better error handling for video playback.

    The current error handling for video playback is minimal. Consider adding more robust error handling and recovery mechanisms.

    if (video) {
      // Start playing the video
      video.play().catch(err => {
        console.error('Failed to play video:', err)
        handleError()
    +    // Implement retry logic or more detailed error feedback
    +    if (err.name === 'NotAllowedError') {
    +      console.warn('Autoplay prevented by browser. User interaction required.')
    +    } else if (err.name === 'NotSupportedError') {
    +      console.error('Video format not supported by this browser')
    +    }
      })
    
      // ...
    }
    📝 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. Thoroughly test & benchmark the code to ensure it meets the requirements.

    Suggested change
    if (video) {
    // Start playing the video
    video.play().catch(err => {
    console.error('Failed to play video:', err)
    handleError()
    })
    // Update texture in animation loop
    mesh.onBeforeRender = () => {
    if (video.readyState === video.HAVE_ENOUGH_DATA) {
    if (material.map !== texture) {
    material.map = texture
    material.needsUpdate = true
    }
    texture.needsUpdate = true
    }
    }
    }
    if (video) {
    // Start playing the video
    video.play().catch(err => {
    console.error('Failed to play video:', err)
    handleError()
    // Implement retry logic or more detailed error feedback
    if (err.name === 'NotAllowedError') {
    console.warn('Autoplay prevented by browser. User interaction required.')
    } else if (err.name === 'NotSupportedError') {
    console.error('Video format not supported by this browser')
    }
    })
    // Update texture in animation loop
    mesh.onBeforeRender = () => {
    if (video.readyState === video.HAVE_ENOUGH_DATA) {
    if (material.map !== texture) {
    material.map = texture
    material.needsUpdate = true
    }
    texture.needsUpdate = true
    }
    }
    }

    ✅ Addressed in commit 3c30cd3

    Copy link

    Deployed to Vercel Preview: https://prismarine-akh6wqc7y-zaro.vercel.app
    Playground
    Storybook

    @zardoy
    Copy link
    Owner Author

    zardoy commented Mar 18, 2025

    @Phoenix616 I finished it. You can use it to load images in high-quality resolution (I'm still grateful you implemented map support some time ago as its nice to have compatible options for existing servers/solutions). By default, I restricted loading from other origins to avoid abuse like client IP leaking when source can be changed by someone else

    test: addTestVideo()
    image: addTestVideo(0, 1, true)

    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: 0

    🧹 Nitpick comments (5)
    renderer/viewer/lib/worldrendererThree.ts (5)

    743-855: Debug visualization should be conditional

    The positionMeshExact method creates several debug meshes that are always added to the scene. In a production environment, these should be conditionally included based on a debug flag.

    positionMeshExact (
      mesh: THREE.Mesh,
      rotation: number,
      startPosition: { x: number, y: number, z: number },
      width: number,
      height: number,
      depth = 1,
    + debug = false
    ) {
      // ... existing positioning code ...
    
      // Create a group to hold our mesh and markers
      const debugGroup = new THREE.Group()
      debugGroup.add(mesh)
    
    + if (debug) {
        // Add a marker at the starting position (should be exactly at pos)
        const startMarker = new THREE.Mesh(
          new THREE.BoxGeometry(0.1, 0.1, 0.1),
          new THREE.MeshBasicMaterial({ color: 0xff_00_00 })
        )
        startMarker.position.copy(new THREE.Vector3(startPosition.x, startPosition.y, startPosition.z))
        debugGroup.add(startMarker)
    
        // ... other debug markers and helpers ...
    + }
    
      return {
        mesh,
        debugGroup
      }
    }

    675-685: Add error recovery for video playback operations

    The video control methods like setVideoPlaying should include better error handling to ensure robustness when controlling videos.

    setVideoPlaying (id: string, playing: boolean) {
      const videoData = this.customMedia.get(id)
      if (videoData?.video) {
        if (playing) {
    -      videoData.video.play().catch(console.error)
    +      videoData.video.play().catch(err => {
    +        console.error(`Failed to play video ${id}:`, err)
    +        // Implement retry logic or user notification
    +        if (err.name === 'NotAllowedError') {
    +          console.warn('Autoplay prevented by browser. User interaction required.')
    +          // You might want to add a UI indicator that user interaction is needed
    +        }
    +      })
        } else {
          videoData.video.pause()
        }
      }
    }

    857-895: Remove or conditionally include test methods in production code

    The test methods createTestCanvasTexture and addTestMeshExact should not be included in production code or should be conditionally excluded in production builds.

    Consider moving these methods to a separate debug/development module that's only included in development builds, or add a condition to disable them in production:

    + // Development/testing utilities - not used in production
    + #if DEBUG
    createTestCanvasTexture() {
      // ...existing code...
    }
    
    addTestMeshExact(rotationNum: number) {
      // ...existing code...
    }
    + #endif

    686-706: Video controls methods should validate input parameters

    The media control methods accept parameters without validation. Add range checks to ensure values are within expected bounds.

    setVideoSeeking (id: string, seconds: number) {
      const videoData = this.customMedia.get(id)
      if (videoData?.video) {
    +    // Ensure seconds is within valid range
    +    if (seconds < 0) {
    +      console.warn(`Invalid seek time: ${seconds}. Setting to 0.`)
    +      seconds = 0
    +    } else if (videoData.video.duration && seconds > videoData.video.duration) {
    +      console.warn(`Seek time exceeds video duration. Setting to end of video.`)
    +      seconds = videoData.video.duration
    +    }
        videoData.video.currentTime = seconds
      }
    }
    
    setVideoVolume (id: string, volume: number) {
      const videoData = this.customMedia.get(id)
      if (videoData?.video) {
    +    // Clamp volume between 0 and 1
    +    volume = Math.max(0, Math.min(1, volume))
        videoData.video.volume = volume
      }
    }
    
    setVideoSpeed (id: string, speed: number) {
      const videoData = this.customMedia.get(id)
      if (videoData?.video) {
    +    // Ensure reasonable playback rate
    +    if (speed <= 0) {
    +      console.warn(`Invalid playback speed: ${speed}. Setting to 0.1.`)
    +      speed = 0.1
    +    } else if (speed > 16) {
    +      console.warn(`Excessive playback speed: ${speed}. Setting to 16.`)
    +      speed = 16
    +    }
        videoData.video.playbackRate = speed
      }
    }

    605-626: Consider alternative texture loading strategy for images

    The current image loading implementation could be enhanced with a loading manager to handle multiple image loads and provide better error reporting.

    + // Add this to the class declaration
    + private textureLoader: THREE.TextureLoader;
    +
    + constructor(...) {
    +   // Existing code...
    +   this.textureLoader = new THREE.TextureLoader();
    +   
    +   // Set up global error handling
    +   this.textureLoader.manager.onError = (url) => {
    +     console.error(`Failed to load texture: ${url}`);
    +   };
    + }
    
    // Then in the addMedia method:
    - const texture = video
    -   ? new THREE.VideoTexture(video)
    -   : new THREE.TextureLoader().load(props.src, () => {
    -     if (this.customMedia.get(id)?.texture === texture) {
    -       material.map = texture
    -       material.needsUpdate = true
    -     }
    -   }, undefined, handleError) // todo cache
    
    + const texture = video
    +   ? new THREE.VideoTexture(video)
    +   : this.textureLoader.load(
    +       props.src,
    +       (loadedTexture) => {
    +         if (this.customMedia.get(id)?.texture === texture) {
    +           material.map = loadedTexture;
    +           material.needsUpdate = true;
    +         }
    +       },
    +       undefined,
    +       handleError
    +     );
    📜 Review details

    Configuration used: CodeRabbit UI
    Review profile: CHILL
    Plan: Pro

    📥 Commits

    Reviewing files that changed from the base of the PR and between 042f0c9 and 3c30cd3.

    📒 Files selected for processing (1)
    • renderer/viewer/lib/worldrendererThree.ts (3 hunks)
    🔇 Additional comments (8)
    renderer/viewer/lib/worldrendererThree.ts (8)

    19-29: Well-designed MediaProperties interface

    The interface is comprehensive and covers all the necessary properties for positioning, sizing, and configuring media elements. The optional parameters provide good flexibility for different use cases.


    43-49: Good choice of Map structure for media tracking

    Using a Map with string IDs as keys is appropriate for storing and retrieving media objects. The structure contains all necessary components (mesh, video element, texture, and UV mapping function).


    479-502: Position calculation handles rotation effectively

    The method correctly calculates positions based on rotation values, ensuring consistent positioning regardless of the rotation angle.


    562-574: Security validation properly implemented

    Good practice to validate origin and destroy existing media with the same ID before creating a new one. The file type detection is simple but effective.


    627-644: Video texture update optimization needed

    The current implementation updates textures inside individual onBeforeRender callbacks, which could lead to performance issues with multiple videos. Consider consolidating texture updates into a single animation loop.

    -      mesh.onBeforeRender = () => {
    -        if (video.readyState === video.HAVE_ENOUGH_DATA) {
    -          if (material.map !== texture) {
    -            material.map = texture
    -            material.needsUpdate = true
    -          }
    -          texture.needsUpdate = true
    -        }
    -      }
    
    // Instead, add a single update method to the class:
    + private updateAllVideoTextures() {
    +   for (const [id, mediaData] of this.customMedia.entries()) {
    +     if (mediaData.video && mediaData.video.readyState === mediaData.video.HAVE_ENOUGH_DATA) {
    +       const mesh = mediaData.mesh.children[0] as THREE.Mesh;
    +       const material = mesh.material as THREE.MeshBasicMaterial;
    +       
    +       if (material.map !== mediaData.texture) {
    +         material.map = mediaData.texture;
    +         material.needsUpdate = true;
    +       }
    +       mediaData.texture.needsUpdate = true;
    +     }
    +   }
    + }
    
    // And call this in your render method:
    + render() {
    +   // Existing code...
    +   this.updateAllVideoTextures();
    +   // Render scene...
    + }

    708-729: Thorough resource cleanup in destroyMedia

    The method properly cleans up all resources, including pausing and clearing the video source, removing meshes from the scene, and disposing of textures and geometries. This is essential to prevent memory leaks.


    504-531: Good error handling with fallback textures

    Creating error textures as fallbacks is a good practice, ensuring visual feedback when media fails to load.


    646-664: UV mapping implementation is flexible and reusable

    The UV mapping configuration is well-implemented, allowing for texture cropping and scaling. Good choice to make it a separate function that can be called after initialization.

    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: 0

    ♻️ Duplicate comments (4)
    src/customChannels.ts (4)

    154-161: ⚠️ Potential issue

    Fix field naming inconsistency in packet structure.

    There's an inconsistency between the field names in the packet structure (prefixed with underscore) and their usage in the handler on line 199 (without underscore). For example, _volume, _background, and _opacity are defined with underscores but accessed without them.

    -      { name: '_volume', type: 'f32' }, // 0
    +      { name: 'volume', type: 'f32' }, // 0
    -      { name: '_aspectRatioMode', type: 'i16' }, // 0
    +      { name: 'aspectRatioMode', type: 'i16' }, // 0
    -      { name: '_background', type: 'i16' }, // 0
    +      { name: 'background', type: 'i16' }, // 0
    -      { name: '_opacity', type: 'i16' }, // 1
    +      { name: 'opacity', type: 'i16' }, // 1
    -      { name: '_cropXStart', type: 'f32' }, // 0
    +      { name: 'cropXStart', type: 'f32' }, // 0
    -      { name: '_cropYStart', type: 'f32' }, // 0
    +      { name: 'cropYStart', type: 'f32' }, // 0
    -      { name: '_cropXEnd', type: 'f32' }, // 0
    +      { name: 'cropXEnd', type: 'f32' }, // 0
    -      { name: '_cropYEnd', type: 'f32' }, // 0
    +      { name: 'cropYEnd', type: 'f32' }, // 0

    198-226: 🛠️ Refactor suggestion

    Add error handling for media operations.

    The current implementation lacks error handling for potential issues like invalid URLs or playback failures. Media operations can fail due to various reasons including network issues, unsupported formats, or CORS restrictions.

    bot._client.on(ADD_CHANNEL as any, (data) => {
      const { id, x, y, z, width, height, rotation, source, loop, background, opacity } = data
    
      const worldRenderer = viewer.world as WorldRendererThree
    
      // Destroy existing video if it exists
      worldRenderer.destroyMedia(id)
    
    +  try {
        // Add new video
        worldRenderer.addMedia(id, {
          position: { x, y, z },
          size: { width, height },
          // side: 'towards',
          src: source,
          rotation: rotation as 0 | 1 | 2 | 3,
          doubleSide: false,
          background,
          opacity: opacity / 100,
          allowOrigins: options.remoteContentNotSameOrigin === false ? [getCurrentTopDomain()] : options.remoteContentNotSameOrigin
        })
    
        // Set loop state
        if (!loop) {
          const videoData = worldRenderer.customMedia.get(id)
          if (videoData?.video) {
            videoData.video.loop = false
          }
        }
    +  } catch (error) {
    +    console.error(`Failed to add media ${id}:`, error)
    +  }
    })

    228-233: 🛠️ Refactor suggestion

    Add error handling for media control operations.

    Similar to the Add handler, these control operations lack error handling. Wrap the operations in try-catch blocks to gracefully handle failures.

    // Example for one handler (apply similar pattern to all):
    bot._client.on(PLAY_CHANNEL as any, (data) => {
      const { id } = data
      const worldRenderer = viewer.world as WorldRendererThree
    +  try {
        worldRenderer.setVideoPlaying(id, true)
    +  } catch (error) {
    +    console.error(`Failed to play video ${id}:`, error)
    +  }
    })

    Also applies to: 235-240, 242-247, 249-254, 256-261, 263-268


    329-361: 🛠️ Refactor suggestion

    Add error handling to addTestVideo and address TODO comment.

    The function contains a TODO comment about broken UV mapping that should be addressed or documented better. Also, it needs error handling for media loading failures.

    const addTestVideo = (rotation = 0 as 0 | 1 | 2 | 3, scale = 1, isImage = false) => {
    +  try {
        const block = window.cursorBlockRel()
        if (!block) return
        const { position: startPosition } = block
    
        const worldRenderer = viewer.world as WorldRendererThree
    
        // Add video with proper positioning
        worldRenderer.addMedia('test-video', {
          position: {
            x: startPosition.x,
            y: startPosition.y + 1,
            z: startPosition.z
          },
          size: {
            width: scale,
            height: scale
          },
          src: isImage ? 'https://bucket.mcraft.fun/test_image.png' : 'https://bucket.mcraft.fun/test_video.mp4',
          rotation,
          // doubleSide: true,
          background: 0x00_00_00, // Black color
          // TODO broken
          // uvMapping: {
          //   startU: 0,
          //   endU: 1,
          //   startV: 0,
          //   endV: 1
          // },
          opacity: 1,
          allowOrigins: true,
        })
    +  } catch (error) {
    +    console.error('Error in addTestVideo:', error)
    +  }
    }

    Also, consider creating a GitHub issue to track the broken UV mapping functionality mentioned in the TODO comment.

    Would you like me to create an issue to track fixing the UV mapping functionality?

    🧹 Nitpick comments (3)
    src/customChannels.ts (3)

    216-216: Validate and document CORS configuration options.

    The code uses options.remoteContentNotSameOrigin for CORS configuration, but its purpose and valid values aren't clearly documented in the code. Consider adding validation and documentation.

    -      allowOrigins: options.remoteContentNotSameOrigin === false ? [getCurrentTopDomain()] : options.remoteContentNotSameOrigin
    +      // allowOrigins config:
    +      // - false: Only allow same domain (more secure)
    +      // - true: Allow all origins (less secure)
    +      // - Array of strings: Allow specific domains
    +      allowOrigins: options.remoteContentNotSameOrigin === false 
    +        ? [getCurrentTopDomain()] 
    +        : (Array.isArray(options.remoteContentNotSameOrigin) || options.remoteContentNotSameOrigin === true 
    +            ? options.remoteContentNotSameOrigin 
    +            : [getCurrentTopDomain()]),

    306-307: Use TypeScript type guards to ensure safe property access.

    The code assumes that videoData.mesh.children[0] is a THREE.Mesh, but this should be verified with a type guard to prevent runtime errors.

      // Check intersection with all video meshes
      for (const [id, videoData] of worldRenderer.customMedia.entries()) {
        // Get the actual mesh (first child of the group)
    -    const mesh = videoData.mesh.children[0] as THREE.Mesh
    -    if (!mesh) continue
    +    if (!videoData.mesh?.children?.length) continue
    +    const child = videoData.mesh.children[0]
    +    if (!(child instanceof THREE.Mesh)) continue
    +    const mesh = child

    205-215: Improve validation of media properties.

    The code doesn't validate important properties like position, size, and source URL. Adding validation would prevent potential rendering issues or crashes.

      // Destroy existing video if it exists
      worldRenderer.destroyMedia(id)
    
      try {
    +    // Validate required parameters
    +    if (!source) {
    +      throw new Error('Media source URL is required')
    +    }
    +    
    +    if (typeof x !== 'number' || typeof y !== 'number' || typeof z !== 'number') {
    +      throw new Error('Invalid position coordinates')
    +    }
    +    
    +    if (typeof width !== 'number' || width <= 0 || typeof height !== 'number' || height <= 0) {
    +      throw new Error('Invalid size dimensions')
    +    }
    +    
        // Add new video
        worldRenderer.addMedia(id, {
          position: { x, y, z },
          size: { width, height },
    📜 Review details

    Configuration used: CodeRabbit UI
    Review profile: CHILL
    Plan: Pro

    📥 Commits

    Reviewing files that changed from the base of the PR and between 3c30cd3 and 7c3cae7.

    📒 Files selected for processing (1)
    • src/customChannels.ts (4 hunks)
    ⏰ Context from checks skipped due to timeout of 90000ms (1)
    • GitHub Check: build-and-deploy
    🔇 Additional comments (1)
    src/customChannels.ts (1)

    364-383:

    ✅ Verification successful

    Enhance domain detection logic and add unit tests.

    The getCurrentTopDomain function handles some special cases for domain detection, but might not cover all possibilities (like .co.jp, .org.uk, etc.). Consider using an established library or expanding the logic.

    function getCurrentTopDomain (): string {
      const { hostname } = location
      // Split hostname into parts
      const parts = hostname.split('.')
    
      // Handle special cases like co.uk, com.br, etc.
      if (parts.length > 2) {
        // Check for common country codes with additional segments
    +    // Known TLDs that commonly have subdomains
    +    const knownTLDs = ['co', 'com', 'org', 'gov', 'edu', 'ac', 'net']
    -    if (parts.at(-2) === 'co' ||
    -      parts.at(-2) === 'com' ||
    -      parts.at(-2) === 'org' ||
    -      parts.at(-2) === 'gov') {
    +    if (knownTLDs.includes(parts.at(-2) || '')) {
          // Return last 3 parts (e.g., example.co.uk)
          return parts.slice(-3).join('.')
        }
      }
    
      // Return last 2 parts (e.g., example.com)
      return parts.slice(-2).join('.')
    }

    Let's check if this function works correctly for various domains:


    🏁 Script executed:

    #!/bin/bash
    # Test the getCurrentTopDomain function with various domains
    
    cat << 'EOF' > test-domains.js
    const domains = [
      'example.com',
      'sub.example.com',
      'example.co.uk',
      'sub.example.co.uk',
      'example.com.br',
      'example.org.uk',
      'example.net.au',
      'localhost'
    ];
    
    // Simulated implementation
    function getCurrentTopDomain(hostname) {
      const parts = hostname.split('.');
      
      if (parts.length > 2) {
        const knownTLDs = ['co', 'com', 'org', 'gov', 'edu', 'ac', 'net'];
        if (knownTLDs.includes(parts.at(-2))) {
          return parts.slice(-3).join('.');
        }
      }
      
      return parts.slice(-2).join('.');
    }
    
    // Test each domain
    console.log('Domain Tests:');
    domains.forEach(domain => {
      console.log(`${domain} -> ${getCurrentTopDomain(domain)}`);
    });
    EOF
    
    node test-domains.js

    Length of output: 288


    The enhanced domain detection logic and unit tests pass all the current test cases.

    • The new knownTLDs list effectively detects domains like example.co.uk, example.com.br, example.org.uk, and more.
    • The unit tests confirm that the function returns the expected top-level domain for each tested input.
    • While this enhancement improves coverage, consider future enhancements if additional edge cases (e.g., domains like .co.jp) need to be supported.

    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: 0

    🧹 Nitpick comments (3)
    src/customChannels.ts (1)

    16-17: Consider renaming for clarity.
    Having two register functions with inconsistent naming (registerMediaChannels vs. registeredJeiChannel) can be confusing. Renaming registeredJeiChannel to registerJeiChannel or similar might improve consistency.

    renderer/viewer/lib/worldrendererThree.ts (2)

    19-29: Interface looks well-defined.
    MediaProperties contains the fields needed for both images and video. Consider adding doc comments if you want more clarity on each field.


    479-506: Potential refactor to unify texture creation.
    createErrorTexture and createBackgroundTexture share some logic (canvas creation, fill style). A shared helper method might reduce duplication.

    📜 Review details

    Configuration used: CodeRabbit UI
    Review profile: CHILL
    Plan: Pro

    📥 Commits

    Reviewing files that changed from the base of the PR and between 7c3cae7 and 65fa505.

    📒 Files selected for processing (2)
    • renderer/viewer/lib/worldrendererThree.ts (3 hunks)
    • src/customChannels.ts (4 hunks)
    🧰 Additional context used
    🧬 Code Definitions (1)
    src/customChannels.ts (4)
    src/mineflayer/plugins/mouse.ts (1) (1)
    • bot (143-150)
    renderer/viewer/lib/worldrendererThree.ts (1) (1)
    • WorldRendererThree (31-871)
    src/optionsStorage.ts (1) (1)
    • options (187-192)
    renderer/viewer/lib/viewer.ts (2) (2)
    • camera (32-34)
    • camera (36-38)
    🔇 Additional comments (22)
    src/customChannels.ts (10)

    2-4: Imports look valid and necessary.
    No issues found—these imports are indeed used later in the file.


    6-6: Import for JEI custom categories is valid.
    This is used later for managing JEI category entries.


    97-97: Packet field naming mismatch.
    The packet structure defines _categoryTitle, but the handler destructures categoryTitle.


    111-115: Logic for empty item list looks correct.
    Removing the category if items === '' is straightforward and appears to fulfill the intended behavior.


    139-290: Add error handling for media operations.
    Although this code successfully registers and handles media channels, there is no try-catch or fallback for failures when adding or controlling media (e.g., invalid URL, network errors, or unsupported formats).


    292-292: Channel constant naming is consistent.
    Declaring MEDIA_INTERACTION_CHANNEL clearly indicates its use case.


    294-296: Simple and effective helper for sending video interactions.
    No issues found with sending the interaction data downstream.


    298-330: Intersection logic is clear and straightforward.
    This function cleanly identifies the hovered video’s UV coordinates. The loop-based approach is fine for a modest number of videos.


    333-365: Include error handling and address the TODO note.
    Like previous suggestions, error handling is missing for the test video function. Also, the // TODO broken comment on UV mapping remains unresolved.


    368-387: Domain extraction logic appears correct.
    Returns the top-level domain for potential CORS handling.

    renderer/viewer/lib/worldrendererThree.ts (12)

    43-48: Data structure for media is solid and flexible.
    Storing each media’s mesh, video element, and texture in a map is straightforward to manage.


    508-528: Background texture creation is concise.
    This snippet is clear with no apparent issues.


    537-619: Performance hint: multiple onBeforeRender watchers might degrade performance.
    This approach works for a small number of videos. However, repeated calls in each frame for many videos might be less efficient than a single playback loop.


    651-660: Setting video playback state is straightforward.
    No issues found with toggling play/pause.


    662-667: Seeking logic is correct.
    Directly setting currentTime is a valid approach.


    669-674: Volume control is implemented properly.
    No issues here.


    676-681: Playback speed control is simple and effective.
    No issues here.


    683-704: Destroying media is thorough.
    Pausing the video, clearing its src, disposing textures and materials, and removing from the scene helps avoid leaks.


    718-731: Positioning logic is correct.
    No immediate concerns with orientation adjustments for the plane geometry.


    832-842: Test canvas texture is fine.
    Simple utility for debug or demonstration.


    844-870: Demonstration mesh method looks fine.
    No issues for a dev/test function.


    530-535:

    ❓ Verification inconclusive

    Verify domain check logic for security.
    Using endsWith(origin) may allow partial matches (e.g., evilmydomain.com matching mydomain.com). If strict subdomain matching is intended, confirm or enhance validation.


    🏁 Script executed:

    #!/bin/bash
    # Checking usage of validateOrigin to see how 'endsWith' is used:
    rg -A 5 "validateOrigin"

    Length of output: 1149


    Action Required: Confirm and Update Domain Validation Logic

    The validateOrigin() method currently uses url.origin.endsWith(origin), which might inadvertently allow partial matches (for example, "evilmydomain.com" would match "mydomain.com"). Please verify if:

    • The intended behavior is to allow subdomain matching.
    • A stricter check (such as an exact comparison or verifying that the allowed origin is preceded by a dot for subdomains) is required for enhanced security.

    If a stricter verification is desired, consider updating the logic. For instance, one alternative might be:

    return allowOrigins.some(origin => 
      url.origin === origin || url.origin.endsWith(`.${origin}`)
    )

    This change ensures either a full match or that the origin is a proper subdomain of the allowed origin.

    • Location: renderer/viewer/lib/worldrendererThree.ts (Lines 530-535)

    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Projects
    None yet
    Development

    Successfully merging this pull request may close these issues.

    2 participants