Skip to content

Commit 3a971dd

Browse files
sh1pt-actions-fleet[bot]ralyodioclaude
authored
feat(chat): play video and audio attachments in the house player (@profullstack/player 0.17.0) (#357)
Decrypted video/audio attachments drew raw <video>/<audio> with native controls. EnhancedVideoPlayer now wraps @profullstack/player (one control bar with auto-hide, full screen including iPhone, compact audio bar) and MessageAttachments renders it. Attachments are blob: URLs with no file extension, so the MIME type is passed and the kind set explicitly. WebRTC call video is untouched. Co-authored-by: Anthony Ettinger <anthony@profullstack.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 42d8d6f commit 3a971dd

4 files changed

Lines changed: 39 additions & 17 deletions

File tree

‎bun.lock‎

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
"@profullstack/footer": "latest",
6666
"@profullstack/openprofile": "^0.2.0",
6767
"@profullstack/pairux-embed": "^0.1.0",
68+
"@profullstack/player": "0.17.0",
6869
"@profullstack/stack": "^0.5.0",
6970
"@profullstack/text-type-detection": "^1.0.0",
7071
"@profullstack/x402-gateway": "^0.16.1",

‎src/lib/components/chat/MessageAttachments.jsx‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { useState, useEffect } from 'react';
44
import { decryptFileContent } from '@/lib/utils/file-decryption.js';
5+
import EnhancedVideoPlayer from '@/lib/components/media/EnhancedVideoPlayer.jsx';
56

67
function base64ToBlobUrl(base64, mimeType) {
78
const bin = atob(base64);
@@ -105,9 +106,9 @@ export default function MessageAttachments({ messageId }) {
105106
<img className="att-image" src={it.url} alt={it.filename} />
106107
</a>
107108
) : it.kind === 'video' ? (
108-
<video className="att-video" src={it.url} controls preload="metadata" />
109+
<EnhancedVideoPlayer className="att-video" src={it.url} type={it.mimeType || 'video/mp4'} />
109110
) : it.kind === 'audio' ? (
110-
<audio className="att-audio" src={it.url} controls preload="metadata" />
111+
<EnhancedVideoPlayer className="att-audio" src={it.url} type={it.mimeType || 'audio/mpeg'} audio />
111112
) : (
112113
<a className="att-file" href={it.url} download={it.filename}>
113114
<span className="att-file-icon">📎</span>
@@ -124,7 +125,7 @@ export default function MessageAttachments({ messageId }) {
124125
.att-error { color: var(--color-error); }
125126
.att-spinner { width: 13px; height: 13px; border: 2px solid rgba(127,127,127,.3); border-top-color: var(--color-brand-primary); border-radius: 50%; animation: spin .8s linear infinite; }
126127
.att-image { max-width: 260px; max-height: 320px; border-radius: .5rem; display: block; cursor: pointer; }
127-
.att-video { max-width: 280px; max-height: 320px; border-radius: .5rem; display: block; background: #000; }
128+
.att-video { width: 280px; max-width: 100%; aspect-ratio: 16 / 9; max-height: 320px; border-radius: .5rem; display: block; background: #000; }
128129
.att-audio { width: 240px; }
129130
.att-file { display: flex; align-items: center; gap: .4rem; padding: .5rem .625rem; background: var(--color-bg-tertiary); border: 1px solid var(--color-border-primary); border-radius: .5rem; text-decoration: none; color: var(--color-text-primary); max-width: 260px; }
130131
.att-file:hover { background: var(--color-bg-secondary); }
Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,33 @@
11
'use client';
22

33
import { useRef, useEffect } from 'react';
4+
import { createPlayer } from '@profullstack/player';
5+
import '@profullstack/player/player.css';
46

5-
export default function EnhancedVideoPlayer({ src, type = 'video/mp4', controls = true, autoPlay = false }) {
6-
const videoRef = useRef(null);
7+
/**
8+
* Plays a video or audio attachment in the house player (@profullstack/player):
9+
* one control bar with auto-hide, full screen including iPhone.
10+
*
11+
* Attachments are decrypted into blob: URLs, which carry no file extension, so
12+
* the caller passes the MIME type and the player is told the kind outright
13+
* instead of sniffing the URL.
14+
*/
15+
export default function EnhancedVideoPlayer({ src, type = 'video/mp4', audio = false, autoPlay = false, className = '' }) {
16+
const rootRef = useRef(null);
717

818
useEffect(() => {
9-
if (videoRef.current && src) {
10-
videoRef.current.load();
11-
}
12-
}, [src]);
19+
const root = rootRef.current;
20+
if (!root || !src) return undefined;
21+
const isAudio = audio || (type || '').toLowerCase().startsWith('audio/');
22+
const handle = createPlayer(root, {
23+
src,
24+
kind: isAudio ? 'audio' : 'mp4',
25+
audio: isAudio,
26+
...(type ? { mimeType: type } : {}),
27+
autoplay: autoPlay,
28+
});
29+
return () => handle.destroy();
30+
}, [src, type, audio, autoPlay]);
1331

14-
return (
15-
<div className="video-player">
16-
<video ref={videoRef} controls={controls} autoPlay={autoPlay} style={{ width: '100%', borderRadius: '.5rem' }}>
17-
<source src={src} type={type} />
18-
Your browser does not support the video tag.
19-
</video>
20-
</div>
21-
);
32+
return <div ref={rootRef} className={`video-player ${className}`.trim()} />;
2233
}

0 commit comments

Comments
 (0)