streamlit-bar-visualizer 0.1.3__tar.gz → 0.1.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {streamlit_bar_visualizer-0.1.3/streamlit_bar_visualizer.egg-info → streamlit_bar_visualizer-0.1.4}/PKG-INFO +38 -16
- streamlit_bar_visualizer-0.1.4/README.md +91 -0
- {streamlit_bar_visualizer-0.1.3 → streamlit_bar_visualizer-0.1.4}/pyproject.toml +1 -1
- {streamlit_bar_visualizer-0.1.3 → streamlit_bar_visualizer-0.1.4}/setup.py +1 -1
- {streamlit_bar_visualizer-0.1.3 → streamlit_bar_visualizer-0.1.4}/streamlit_bar_visualizer/frontend/build/static/js/index.js +1 -1
- {streamlit_bar_visualizer-0.1.3 → streamlit_bar_visualizer-0.1.4/streamlit_bar_visualizer.egg-info}/PKG-INFO +38 -16
- streamlit_bar_visualizer-0.1.3/README.md +0 -69
- {streamlit_bar_visualizer-0.1.3 → streamlit_bar_visualizer-0.1.4}/LICENSE +0 -0
- {streamlit_bar_visualizer-0.1.3 → streamlit_bar_visualizer-0.1.4}/MANIFEST.in +0 -0
- {streamlit_bar_visualizer-0.1.3 → streamlit_bar_visualizer-0.1.4}/setup.cfg +0 -0
- {streamlit_bar_visualizer-0.1.3 → streamlit_bar_visualizer-0.1.4}/streamlit_bar_visualizer/__init__.py +0 -0
- {streamlit_bar_visualizer-0.1.3 → streamlit_bar_visualizer-0.1.4}/streamlit_bar_visualizer/frontend/build/index.html +0 -0
- {streamlit_bar_visualizer-0.1.3 → streamlit_bar_visualizer-0.1.4}/streamlit_bar_visualizer/frontend/build/static/css/index.css +0 -0
- {streamlit_bar_visualizer-0.1.3 → streamlit_bar_visualizer-0.1.4}/streamlit_bar_visualizer.egg-info/SOURCES.txt +0 -0
- {streamlit_bar_visualizer-0.1.3 → streamlit_bar_visualizer-0.1.4}/streamlit_bar_visualizer.egg-info/dependency_links.txt +0 -0
- {streamlit_bar_visualizer-0.1.3 → streamlit_bar_visualizer-0.1.4}/streamlit_bar_visualizer.egg-info/requires.txt +0 -0
- {streamlit_bar_visualizer-0.1.3 → streamlit_bar_visualizer-0.1.4}/streamlit_bar_visualizer.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: streamlit-bar-visualizer
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.4
|
4
4
|
Summary: Beautiful audio frequency visualizer component for Streamlit with multiple state animations
|
5
5
|
Home-page: https://github.com/bensonbs/streamlit-bar-visualizer
|
6
6
|
Author: Benson Sung
|
@@ -35,24 +35,46 @@ pip install streamlit-bar-visualizer
|
|
35
35
|
## Quick Start
|
36
36
|
|
37
37
|
```python
|
38
|
+
import streamlit as st
|
38
39
|
from streamlit_bar_visualizer import bar_visualizer
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
#
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
40
|
+
import time
|
41
|
+
|
42
|
+
st.title("🎵 Audio Stream Visualizer Test")
|
43
|
+
|
44
|
+
# Session state
|
45
|
+
if 'play_count' not in st.session_state:
|
46
|
+
st.session_state.play_count = 0
|
47
|
+
|
48
|
+
# Audio stream options
|
49
|
+
stream_options = {
|
50
|
+
"Radio Paradise (MP3)": "https://stream.radioparadise.com/mp3-128",
|
51
|
+
"BBC World Service": "https://stream.live.vc.bbcmedia.co.uk/bbc_world_service",
|
52
|
+
"Custom URL": "custom"
|
53
|
+
}
|
54
|
+
|
55
|
+
selected_stream = st.selectbox("Select Audio Stream", list(stream_options.keys()))
|
56
|
+
|
57
|
+
if selected_stream == "Custom URL":
|
58
|
+
stream_url = st.text_input("Enter Audio Stream URL", value="https://stream.radioparadise.com/mp3-128")
|
59
|
+
else:
|
60
|
+
stream_url = stream_options[selected_stream]
|
61
|
+
|
62
|
+
st.info(f"🎧 Current Audio Stream: {stream_url}")
|
63
|
+
|
64
|
+
# Display visualizer
|
65
|
+
if stream_url:
|
66
|
+
bar_visualizer(
|
67
|
+
state="auto",
|
68
|
+
stream_url=stream_url,
|
69
|
+
key=f"visualizer_{st.session_state.play_count}"
|
70
|
+
)
|
71
|
+
|
72
|
+
st.markdown("---")
|
73
|
+
st.caption("💡 Tip: If you don't hear sound, check the browser console (F12) for error messages and verify the audio stream URL is valid.")
|
54
74
|
```
|
55
75
|
|
76
|
+
> ⚠️ **Browser Compatibility Warning**: Safari may have issues with audio streaming due to CORS restrictions. For best results, use **Chrome** or **Firefox**.
|
77
|
+
|
56
78
|
## API
|
57
79
|
|
58
80
|
### `bar_visualizer(state, stream_url, key)`
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# Streamlit Bar Visualizer
|
2
|
+
|
3
|
+
Audio frequency visualizer component for Streamlit, inspired by ElevenLabs UI design.
|
4
|
+
|
5
|
+

|
6
|
+

|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
```bash
|
11
|
+
pip install streamlit-bar-visualizer
|
12
|
+
```
|
13
|
+
|
14
|
+
## Quick Start
|
15
|
+
|
16
|
+
```python
|
17
|
+
import streamlit as st
|
18
|
+
from streamlit_bar_visualizer import bar_visualizer
|
19
|
+
import time
|
20
|
+
|
21
|
+
st.title("🎵 Audio Stream Visualizer Test")
|
22
|
+
|
23
|
+
# Session state
|
24
|
+
if 'play_count' not in st.session_state:
|
25
|
+
st.session_state.play_count = 0
|
26
|
+
|
27
|
+
# Audio stream options
|
28
|
+
stream_options = {
|
29
|
+
"Radio Paradise (MP3)": "https://stream.radioparadise.com/mp3-128",
|
30
|
+
"BBC World Service": "https://stream.live.vc.bbcmedia.co.uk/bbc_world_service",
|
31
|
+
"Custom URL": "custom"
|
32
|
+
}
|
33
|
+
|
34
|
+
selected_stream = st.selectbox("Select Audio Stream", list(stream_options.keys()))
|
35
|
+
|
36
|
+
if selected_stream == "Custom URL":
|
37
|
+
stream_url = st.text_input("Enter Audio Stream URL", value="https://stream.radioparadise.com/mp3-128")
|
38
|
+
else:
|
39
|
+
stream_url = stream_options[selected_stream]
|
40
|
+
|
41
|
+
st.info(f"🎧 Current Audio Stream: {stream_url}")
|
42
|
+
|
43
|
+
# Display visualizer
|
44
|
+
if stream_url:
|
45
|
+
bar_visualizer(
|
46
|
+
state="auto",
|
47
|
+
stream_url=stream_url,
|
48
|
+
key=f"visualizer_{st.session_state.play_count}"
|
49
|
+
)
|
50
|
+
|
51
|
+
st.markdown("---")
|
52
|
+
st.caption("💡 Tip: If you don't hear sound, check the browser console (F12) for error messages and verify the audio stream URL is valid.")
|
53
|
+
```
|
54
|
+
|
55
|
+
> ⚠️ **Browser Compatibility Warning**: Safari may have issues with audio streaming due to CORS restrictions. For best results, use **Chrome** or **Firefox**.
|
56
|
+
|
57
|
+
## API
|
58
|
+
|
59
|
+
### `bar_visualizer(state, stream_url, key)`
|
60
|
+
|
61
|
+
**Parameters:**
|
62
|
+
- `state` (str): Animation state
|
63
|
+
- `"listening"` - Breathing animation
|
64
|
+
- `"speaking"` - Active speaking animation
|
65
|
+
- `"thinking"` - Pulsing animation
|
66
|
+
- `"connecting"` - Wave animation
|
67
|
+
- `"initializing"` - Building up animation
|
68
|
+
- `"auto"` - Auto-switch based on audio playback (thinking → speaking → initializing)
|
69
|
+
- `stream_url` (str, optional): Audio stream URL to visualize
|
70
|
+
- `key` (str, optional): Unique component identifier
|
71
|
+
|
72
|
+
**Returns:** Current component state (str)
|
73
|
+
|
74
|
+
## Development
|
75
|
+
|
76
|
+
```bash
|
77
|
+
# Install dependencies
|
78
|
+
cd streamlit_bar_visualizer/frontend
|
79
|
+
npm install
|
80
|
+
|
81
|
+
# Start dev server
|
82
|
+
npm run start
|
83
|
+
|
84
|
+
# Build for production
|
85
|
+
npm run build
|
86
|
+
```
|
87
|
+
|
88
|
+
## License
|
89
|
+
|
90
|
+
MIT
|
91
|
+
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "streamlit-bar-visualizer"
|
7
|
-
version = "0.1.
|
7
|
+
version = "0.1.4"
|
8
8
|
description = "Beautiful audio frequency visualizer component for Streamlit with multiple state animations"
|
9
9
|
readme = "README.md"
|
10
10
|
requires-python = ">=3.8"
|
@@ -6,7 +6,7 @@ long_description = (this_directory / "README.md").read_text()
|
|
6
6
|
|
7
7
|
setuptools.setup(
|
8
8
|
name="streamlit-bar-visualizer",
|
9
|
-
version="0.1.
|
9
|
+
version="0.1.4",
|
10
10
|
author="Benson Sung",
|
11
11
|
author_email="benson.bs.sung@gmail.com",
|
12
12
|
description="Beautiful audio frequency visualizer component for Streamlit with multiple state animations",
|
@@ -60,4 +60,4 @@ Error generating stack: `+s.message+`
|
|
60
60
|
background-color: var(--background-color);
|
61
61
|
color: var(--text-color);
|
62
62
|
}
|
63
|
-
`)};function j1(e){var t=!1;try{t=e instanceof BigInt64Array||e instanceof BigUint64Array}catch{}return e instanceof Int8Array||e instanceof Uint8Array||e instanceof Uint8ClampedArray||e instanceof Int16Array||e instanceof Uint16Array||e instanceof Int32Array||e instanceof Uint32Array||e instanceof Float32Array||e instanceof Float64Array||t}var sg=function(){var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(r,i){r.__proto__=i}||function(r,i){for(var s in i)Object.prototype.hasOwnProperty.call(i,s)&&(r[s]=i[s])},e(t,n)};return function(t,n){if(typeof n!="function"&&n!==null)throw new TypeError("Class extends value "+String(n)+" is not a constructor or null");e(t,n);function r(){this.constructor=t}t.prototype=n===null?Object.create(n):(r.prototype=n.prototype,new r)}}(),V1=function(e){sg(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.componentDidMount=function(){$e.setFrameHeight()},t.prototype.componentDidUpdate=function(){$e.setFrameHeight()},t}(fr.PureComponent);function $1(e){var t=function(n){sg(r,n);function r(i){var s=n.call(this,i)||this;return s.componentDidMount=function(){$e.events.addEventListener($e.RENDER_EVENT,s.onRenderEvent),$e.setComponentReady()},s.componentDidUpdate=function(){s.state.componentError!=null&&$e.setFrameHeight()},s.componentWillUnmount=function(){$e.events.removeEventListener($e.RENDER_EVENT,s.onRenderEvent)},s.onRenderEvent=function(o){s.setState({renderData:o.detail})},s.state={renderData:void 0,componentError:void 0},s}return r.prototype.render=function(){return this.state.componentError!=null?fr.createElement("div",null,fr.createElement("h1",null,"Component Error"),fr.createElement("span",null,this.state.componentError.message)):this.state.renderData==null?null:fr.createElement(e,{width:window.innerWidth,disabled:this.state.renderData.disabled,args:this.state.renderData.args,theme:this.state.renderData.theme})},r.getDerivedStateFromError=function(i){return{componentError:i}},r}(fr.PureComponent);return ew(t,e)}function og(e){var t,n,r="";if(typeof e=="string"||typeof e=="number")r+=e;else if(typeof e=="object")if(Array.isArray(e)){var i=e.length;for(t=0;t<i;t++)e[t]&&(n=og(e[t]))&&(r&&(r+=" "),r+=n)}else for(n in e)e[n]&&(r&&(r+=" "),r+=n);return r}function W1(){for(var e,t,n=0,r="",i=arguments.length;n<i;n++)(e=arguments[n])&&(t=og(e))&&(r&&(r+=" "),r+=t);return r}function lg(...e){return W1(e)}function H1(e,t={}){const n=new(window.AudioContext||window.webkitAudioContext),r=n.createMediaStreamSource(e),i=n.createAnalyser();return t.fftSize&&(i.fftSize=t.fftSize),t.smoothingTimeConstant!==void 0&&(i.smoothingTimeConstant=t.smoothingTimeConstant),t.minDecibels!==void 0&&(i.minDecibels=t.minDecibels),t.maxDecibels!==void 0&&(i.maxDecibels=t.maxDecibels),r.connect(i),{analyser:i,audioContext:n,cleanup:()=>{r.disconnect(),n.close()}}}const Y1={bands:5,loPass:100,hiPass:600,updateInterval:32,analyserOptions:{fftSize:2048}},Q1=e=>{if(e===-1/0)return 0;const r=1-Math.max(-100,Math.min(-10,e))*-1/100;return Math.sqrt(r)};function K1(e,t={}){var l,a,u,c;const n=ht.useMemo(()=>({...Y1,...t}),[t.bands,t.loPass,t.hiPass,t.updateInterval,(l=t.analyserOptions)==null?void 0:l.fftSize,(a=t.analyserOptions)==null?void 0:a.smoothingTimeConstant,(u=t.analyserOptions)==null?void 0:u.minDecibels,(c=t.analyserOptions)==null?void 0:c.maxDecibels]),[r,i]=ht.useState(()=>new Array(n.bands).fill(0)),s=ht.useRef(new Array(n.bands).fill(0)),o=ht.useRef(void 0);return ht.useEffect(()=>{if(!e){const b=new Array(n.bands).fill(0);i(b),s.current=b;return}const{analyser:f,cleanup:m}=H1(e,n.analyserOptions),g=f.frequencyBinCount,v=new Float32Array(g),S=n.loPass,Q=n.hiPass,h=Q-S,d=Math.ceil(h/n.bands);let y=0;const w=n.updateInterval,I=b=>{if(b-y>=w){f.getFloatFrequencyData(v);const E=new Array(n.bands);for(let W=0;W<n.bands;W++){let A=0,Nt=0;const sn=S+W*d,on=Math.min(S+(W+1)*d,Q);for(let Mr=sn;Mr<on;Mr++)A+=Q1(v[Mr]),Nt++;E[W]=Nt>0?A/Nt:0}let O=!1;for(let W=0;W<E.length;W++)if(Math.abs(E[W]-s.current[W])>.01){O=!0;break}O&&(s.current=E,i(E)),y=b}o.current=requestAnimationFrame(I)};return o.current=requestAnimationFrame(I),()=>{m(),o.current&&cancelAnimationFrame(o.current)}},[e,n]),r}const J1=(e,t,n)=>{const r=ht.useRef(0),[i,s]=ht.useState([]),o=ht.useRef(null),l=ht.useMemo(()=>e==="thinking"||e==="listening"?X1(t):e==="connecting"||e==="initializing"?G1(t):e===void 0||e==="speaking"?[new Array(t).fill(0).map((a,u)=>u)]:[[]],[e,t]);return ht.useEffect(()=>{r.current=0,s(l[0]||[])},[l]),ht.useEffect(()=>{let a=performance.now();const u=c=>{c-a>=n&&(r.current=(r.current+1)%l.length,s(l[r.current]||[]),a=c),o.current=requestAnimationFrame(u)};return o.current=requestAnimationFrame(u),()=>{o.current!==null&&cancelAnimationFrame(o.current)}},[n,l]),i},G1=e=>{const t=[];for(let n=0;n<e;n++)t.push([n,e-1-n]);return t},X1=e=>[[Math.floor(e/2)],[-1]],ag=ht.forwardRef(({state:e,barCount:t=15,mediaStream:n,minHeight:r=20,maxHeight:i=100,demo:s=!1,centerAlign:o=!1,className:l,style:a,...u},c)=>{const f=K1(n,{bands:t,loPass:100,hiPass:200}),m=ht.useRef(new Array(t).fill(.2)),[g,v]=ht.useState(()=>new Array(t).fill(.2)),S=ht.useRef(void 0);ht.useEffect(()=>{if(!s)return;if(e!=="speaking"&&e!=="listening"){const b=new Array(t).fill(.2);m.current=b,v(b);return}let d=0;const y=50,w=Date.now()/1e3,I=b=>{if(b-d>=y){const E=Date.now()/1e3-w,O=new Array(t);for(let A=0;A<t;A++){const Nt=A*.5,sn=Math.sin(E*2+Nt)*.3+.5,on=Math.random()*.2;O[A]=Math.max(.1,Math.min(1,sn+on))}let W=!1;for(let A=0;A<t;A++)if(Math.abs(O[A]-m.current[A])>.05){W=!0;break}W&&(m.current=O,v(O)),d=b}S.current=requestAnimationFrame(I)};return S.current=requestAnimationFrame(I),()=>{S.current&&cancelAnimationFrame(S.current)}},[s,e,t]);const Q=ht.useMemo(()=>s?g:f,[s,g,f]),h=J1(e,t,e==="connecting"?2e3/t:e==="thinking"?150:e==="listening"?500:1e3);return Un.jsx("div",{ref:c,"data-state":e,className:lg("relative flex justify-center gap-1.5",o?"items-center":"items-end","bg-muted h-28 w-full overflow-hidden rounded-lg",l),style:{...a},...u,children:Q.map((d,y)=>{const w=Math.min(i,Math.max(r,d*100+5)),I=(h==null?void 0:h.includes(y))??!1;return Un.jsx(ug,{heightPct:w,isHighlighted:I,state:e},y)})})}),ug=ht.memo(({heightPct:e,isHighlighted:t,state:n})=>Un.jsx("div",{"data-highlighted":t,className:lg("max-w-[12px] min-w-[8px] flex-1 transition-all duration-150","rounded-full","bg-border data-[highlighted=true]:bg-primary",n==="speaking"&&"bg-primary",n==="thinking"&&t&&"animate-pulse"),style:{height:`${e}%`,animationDuration:n==="thinking"?"300ms":void 0}}));ug.displayName="Bar";const cg=ht.memo(ag,(e,t)=>e.state===t.state&&e.barCount===t.barCount&&e.mediaStream===t.mediaStream&&e.minHeight===t.minHeight&&e.maxHeight===t.maxHeight&&e.demo===t.demo&&e.centerAlign===t.centerAlign&&e.className===t.className&&JSON.stringify(e.style)===JSON.stringify(t.style));ag.displayName="BarVisualizerComponent";cg.displayName="BarVisualizer";class Z1 extends V1{constructor(){super(...arguments);Re(this,"state",{mediaStream:null,audioState:"idle"});Re(this,"audioRef",ht.createRef());Re(this,"render",()=>{const n=this.props.args.state||"listening",r=this.props.args.barCount||20,i=this.props.args.minHeight||15,s=this.props.args.maxHeight||90,o=this.props.args.demo!==!1,l=this.props.args.centerAlign||!1,a=this.props.args.streamUrl;let u;if(n==="auto"){switch(this.state.audioState){case"loading":u="thinking";break;case"playing":u="speaking";break;case"ended":u="initializing";break;case"idle":default:u="listening";break}console.log("🤖 Auto mode: audioState =",this.state.audioState,"→ displayState =",u)}else u=n;const c=o||a&&!this.state.mediaStream;return a&&!this.state.mediaStream&&console.log("⏳ 音頻加載中... (UI 已顯示,使用假數據動畫,狀態:",u,")"),Un.jsxs("div",{className:"streamlit-bar-visualizer",children:[a&&Un.jsx("audio",{ref:this.audioRef,src:a,controls:!1,autoPlay:!0,crossOrigin:"anonymous",style:{display:"none"}}),Un.jsx(cg,{mediaStream:this.state.mediaStream,state:u,barCount:r,minHeight:i,maxHeight:s,demo:c,centerAlign:l})]})});Re(this,"componentDidMount",async()=>{const n=this.props.args.streamUrl;if(this.props.args.demo,n&&this.audioRef.current){console.log("🎵 Attempting to load audio stream:",n);const r=this.audioRef.current;this.setState({audioState:"loading"}),r.addEventListener("loadstart",this.handleLoadStart),r.addEventListener("playing",this.handlePlaying),r.addEventListener("ended",this.handleEnded);try{await new Promise((s,o)=>{const l=setTimeout(()=>o(new Error("Audio load timeout")),1e4);r.oncanplay=()=>{clearTimeout(l),s(!0)},r.onerror=a=>{var u,c,f;clearTimeout(l),console.error("❌ Audio element error event:",a),console.error(" Error code:",(u=r.error)==null?void 0:u.code),console.error(" Error message:",(c=r.error)==null?void 0:c.message),console.error(" Network state:",r.networkState),console.error(" Ready state:",r.readyState),o(new Error(`Audio load error: ${((f=r.error)==null?void 0:f.message)||"Unknown error"}`))}}),r.muted=!0,await r.play(),console.log("✅ Audio stream is playing (initially muted)"),await new Promise(s=>setTimeout(s,100));const i=r;if(typeof i.captureStream=="function"){const s=i.captureStream();this.setState({mediaStream:s}),console.log("✅ Audio stream captured successfully via captureStream()"),r.muted=!1,console.log("🔊 Audio unmuted - you should now hear the sound")}else if(typeof i.mozCaptureStream=="function"){const s=i.mozCaptureStream();this.setState({mediaStream:s}),console.log("✅ Audio stream captured successfully via mozCaptureStream() (Firefox)"),r.muted=!1,console.log("🔊 Audio unmuted - you should now hear the sound")}else console.error("❌ HTMLAudioElement.captureStream() is not supported in this browser.")}catch(i){console.error("❌ Error playing or capturing audio stream:",i),console.warn("⚠️ Audio stream failed. Not falling back to microphone. Check console for errors.")}}else console.log("🎭 No audio stream provided - running in demo mode (using fake audio data)");$e.setFrameHeight()});Re(this,"componentWillUnmount",()=>{if(this.state.mediaStream&&!this.props.args.streamUrl&&this.state.mediaStream.getTracks().forEach(n=>n.stop()),this.audioRef.current){const n=this.audioRef.current;n.pause(),n.removeEventListener("loadstart",this.handleLoadStart),n.removeEventListener("playing",this.handlePlaying),n.removeEventListener("ended",this.handleEnded)}});Re(this,"componentDidUpdate",async n=>{const r=n.args.streamUrl,i=this.props.args.streamUrl,s=r!==i;if(i&&this.audioRef.current&&(s||this.state.audioState==="ended")){console.log("🔄 Reloading audio:",s?"URL changed":"Replay same URL"),this.setState({mediaStream:null,audioState:"loading"});const l=this.audioRef.current;l.removeEventListener("loadstart",this.handleLoadStart),l.removeEventListener("playing",this.handlePlaying),l.removeEventListener("ended",this.handleEnded),l.addEventListener("loadstart",this.handleLoadStart),l.addEventListener("playing",this.handlePlaying),l.addEventListener("ended",this.handleEnded),s?l.src=i:l.load();try{l.muted=!0,await l.play();const a=l;if(typeof a.captureStream=="function"){const u=a.captureStream();this.setState({mediaStream:u}),console.log("✅ Audio stream updated and captured successfully"),l.muted=!1,console.log("🔊 Audio unmuted")}else if(typeof a.mozCaptureStream=="function"){const u=a.mozCaptureStream();this.setState({mediaStream:u}),console.log("✅ Audio stream updated and captured successfully (Firefox)"),l.muted=!1,console.log("🔊 Audio unmuted")}}catch(a){console.error("❌ Error updating audio stream:",a)}}$e.setFrameHeight()});Re(this,"handleLoadStart",()=>{console.log("📥 Audio loadstart"),this.setState({audioState:"loading"})});Re(this,"handlePlaying",()=>{console.log("▶️ Audio playing"),this.setState({audioState:"playing"})});Re(this,"handleEnded",()=>{console.log("⏹️ Audio ended"),this.setState({audioState:"ended"})})}}const q1=$1(Z1);zv.render(Un.jsx(fr.StrictMode,{children:Un.jsx(q1,{})}),document.getElementById("root"));
|
63
|
+
`)};function j1(e){var t=!1;try{t=e instanceof BigInt64Array||e instanceof BigUint64Array}catch{}return e instanceof Int8Array||e instanceof Uint8Array||e instanceof Uint8ClampedArray||e instanceof Int16Array||e instanceof Uint16Array||e instanceof Int32Array||e instanceof Uint32Array||e instanceof Float32Array||e instanceof Float64Array||t}var sg=function(){var e=function(t,n){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(r,i){r.__proto__=i}||function(r,i){for(var s in i)Object.prototype.hasOwnProperty.call(i,s)&&(r[s]=i[s])},e(t,n)};return function(t,n){if(typeof n!="function"&&n!==null)throw new TypeError("Class extends value "+String(n)+" is not a constructor or null");e(t,n);function r(){this.constructor=t}t.prototype=n===null?Object.create(n):(r.prototype=n.prototype,new r)}}(),V1=function(e){sg(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.componentDidMount=function(){$e.setFrameHeight()},t.prototype.componentDidUpdate=function(){$e.setFrameHeight()},t}(fr.PureComponent);function $1(e){var t=function(n){sg(r,n);function r(i){var s=n.call(this,i)||this;return s.componentDidMount=function(){$e.events.addEventListener($e.RENDER_EVENT,s.onRenderEvent),$e.setComponentReady()},s.componentDidUpdate=function(){s.state.componentError!=null&&$e.setFrameHeight()},s.componentWillUnmount=function(){$e.events.removeEventListener($e.RENDER_EVENT,s.onRenderEvent)},s.onRenderEvent=function(o){s.setState({renderData:o.detail})},s.state={renderData:void 0,componentError:void 0},s}return r.prototype.render=function(){return this.state.componentError!=null?fr.createElement("div",null,fr.createElement("h1",null,"Component Error"),fr.createElement("span",null,this.state.componentError.message)):this.state.renderData==null?null:fr.createElement(e,{width:window.innerWidth,disabled:this.state.renderData.disabled,args:this.state.renderData.args,theme:this.state.renderData.theme})},r.getDerivedStateFromError=function(i){return{componentError:i}},r}(fr.PureComponent);return ew(t,e)}function og(e){var t,n,r="";if(typeof e=="string"||typeof e=="number")r+=e;else if(typeof e=="object")if(Array.isArray(e)){var i=e.length;for(t=0;t<i;t++)e[t]&&(n=og(e[t]))&&(r&&(r+=" "),r+=n)}else for(n in e)e[n]&&(r&&(r+=" "),r+=n);return r}function W1(){for(var e,t,n=0,r="",i=arguments.length;n<i;n++)(e=arguments[n])&&(t=og(e))&&(r&&(r+=" "),r+=t);return r}function lg(...e){return W1(e)}function H1(e,t={}){const n=new(window.AudioContext||window.webkitAudioContext),r=n.createMediaStreamSource(e),i=n.createAnalyser();return t.fftSize&&(i.fftSize=t.fftSize),t.smoothingTimeConstant!==void 0&&(i.smoothingTimeConstant=t.smoothingTimeConstant),t.minDecibels!==void 0&&(i.minDecibels=t.minDecibels),t.maxDecibels!==void 0&&(i.maxDecibels=t.maxDecibels),r.connect(i),{analyser:i,audioContext:n,cleanup:()=>{r.disconnect(),n.close()}}}const Y1={bands:5,loPass:100,hiPass:600,updateInterval:32,analyserOptions:{fftSize:2048}},Q1=e=>{if(e===-1/0)return 0;const r=1-Math.max(-100,Math.min(-10,e))*-1/100;return Math.sqrt(r)};function K1(e,t={}){var l,a,u,c;const n=ht.useMemo(()=>({...Y1,...t}),[t.bands,t.loPass,t.hiPass,t.updateInterval,(l=t.analyserOptions)==null?void 0:l.fftSize,(a=t.analyserOptions)==null?void 0:a.smoothingTimeConstant,(u=t.analyserOptions)==null?void 0:u.minDecibels,(c=t.analyserOptions)==null?void 0:c.maxDecibels]),[r,i]=ht.useState(()=>new Array(n.bands).fill(0)),s=ht.useRef(new Array(n.bands).fill(0)),o=ht.useRef(void 0);return ht.useEffect(()=>{if(!e){const b=new Array(n.bands).fill(0);i(b),s.current=b;return}const{analyser:f,cleanup:m}=H1(e,n.analyserOptions),g=f.frequencyBinCount,v=new Float32Array(g),S=n.loPass,Q=n.hiPass,h=Q-S,d=Math.ceil(h/n.bands);let y=0;const w=n.updateInterval,I=b=>{if(b-y>=w){f.getFloatFrequencyData(v);const E=new Array(n.bands);for(let W=0;W<n.bands;W++){let A=0,Nt=0;const sn=S+W*d,on=Math.min(S+(W+1)*d,Q);for(let Mr=sn;Mr<on;Mr++)A+=Q1(v[Mr]),Nt++;E[W]=Nt>0?A/Nt:0}let O=!1;for(let W=0;W<E.length;W++)if(Math.abs(E[W]-s.current[W])>.01){O=!0;break}O&&(s.current=E,i(E)),y=b}o.current=requestAnimationFrame(I)};return o.current=requestAnimationFrame(I),()=>{m(),o.current&&cancelAnimationFrame(o.current)}},[e,n]),r}const J1=(e,t,n)=>{const r=ht.useRef(0),[i,s]=ht.useState([]),o=ht.useRef(null),l=ht.useMemo(()=>e==="thinking"||e==="listening"?X1(t):e==="connecting"||e==="initializing"?G1(t):e===void 0||e==="speaking"?[new Array(t).fill(0).map((a,u)=>u)]:[[]],[e,t]);return ht.useEffect(()=>{r.current=0,s(l[0]||[])},[l]),ht.useEffect(()=>{let a=performance.now();const u=c=>{c-a>=n&&(r.current=(r.current+1)%l.length,s(l[r.current]||[]),a=c),o.current=requestAnimationFrame(u)};return o.current=requestAnimationFrame(u),()=>{o.current!==null&&cancelAnimationFrame(o.current)}},[n,l]),i},G1=e=>{const t=[];for(let n=0;n<e;n++)t.push([n,e-1-n]);return t},X1=e=>[[Math.floor(e/2)],[-1]],ag=ht.forwardRef(({state:e,barCount:t=15,mediaStream:n,minHeight:r=20,maxHeight:i=100,demo:s=!1,centerAlign:o=!1,className:l,style:a,...u},c)=>{const f=K1(n,{bands:t,loPass:100,hiPass:200}),m=ht.useRef(new Array(t).fill(.2)),[g,v]=ht.useState(()=>new Array(t).fill(.2)),S=ht.useRef(void 0);ht.useEffect(()=>{if(!s)return;if(e!=="speaking"&&e!=="listening"){const b=new Array(t).fill(.2);m.current=b,v(b);return}let d=0;const y=50,w=Date.now()/1e3,I=b=>{if(b-d>=y){const E=Date.now()/1e3-w,O=new Array(t);for(let A=0;A<t;A++){const Nt=A*.5,sn=Math.sin(E*2+Nt)*.3+.5,on=Math.random()*.2;O[A]=Math.max(.1,Math.min(1,sn+on))}let W=!1;for(let A=0;A<t;A++)if(Math.abs(O[A]-m.current[A])>.05){W=!0;break}W&&(m.current=O,v(O)),d=b}S.current=requestAnimationFrame(I)};return S.current=requestAnimationFrame(I),()=>{S.current&&cancelAnimationFrame(S.current)}},[s,e,t]);const Q=ht.useMemo(()=>s?g:f,[s,g,f]),h=J1(e,t,e==="connecting"?2e3/t:e==="thinking"?150:e==="listening"?500:1e3);return Un.jsx("div",{ref:c,"data-state":e,className:lg("relative flex justify-center gap-1.5",o?"items-center":"items-end","bg-muted h-28 w-full overflow-hidden rounded-lg",l),style:{...a},...u,children:Q.map((d,y)=>{const w=Math.min(i,Math.max(r,d*100+5)),I=(h==null?void 0:h.includes(y))??!1;return Un.jsx(ug,{heightPct:w,isHighlighted:I,state:e},y)})})}),ug=ht.memo(({heightPct:e,isHighlighted:t,state:n})=>Un.jsx("div",{"data-highlighted":t,className:lg("max-w-[12px] min-w-[8px] flex-1 transition-all duration-150","rounded-full","bg-border data-[highlighted=true]:bg-primary",n==="speaking"&&"bg-primary",n==="thinking"&&t&&"animate-pulse"),style:{height:`${e}%`,animationDuration:n==="thinking"?"300ms":void 0}}));ug.displayName="Bar";const cg=ht.memo(ag,(e,t)=>e.state===t.state&&e.barCount===t.barCount&&e.mediaStream===t.mediaStream&&e.minHeight===t.minHeight&&e.maxHeight===t.maxHeight&&e.demo===t.demo&&e.centerAlign===t.centerAlign&&e.className===t.className&&JSON.stringify(e.style)===JSON.stringify(t.style));ag.displayName="BarVisualizerComponent";cg.displayName="BarVisualizer";class Z1 extends V1{constructor(){super(...arguments);Re(this,"state",{mediaStream:null,audioState:"idle"});Re(this,"audioRef",ht.createRef());Re(this,"render",()=>{const n=this.props.args.state||"listening",r=this.props.args.barCount||20,i=this.props.args.minHeight||15,s=this.props.args.maxHeight||90,o=this.props.args.demo!==!1,l=this.props.args.centerAlign||!1,a=this.props.args.streamUrl;let u;if(n==="auto"){switch(this.state.audioState){case"loading":u="thinking";break;case"playing":u="speaking";break;case"ended":u="initializing";break;case"idle":default:u="listening";break}console.log("🤖 Auto mode: audioState =",this.state.audioState,"→ displayState =",u)}else u=n;const c=o||a&&!this.state.mediaStream;return a&&!this.state.mediaStream&&console.log("⏳ 音頻加載中... (UI 已顯示,使用假數據動畫,狀態:",u,")"),Un.jsxs("div",{className:"streamlit-bar-visualizer",children:[a&&Un.jsx("audio",{ref:this.audioRef,src:a,controls:!1,autoPlay:!0,crossOrigin:"anonymous",style:{display:"none"}}),Un.jsx(cg,{mediaStream:this.state.mediaStream,state:u,barCount:r,minHeight:i,maxHeight:s,demo:c,centerAlign:l})]})});Re(this,"componentDidMount",async()=>{const n=this.props.args.streamUrl;if(this.props.args.demo,n&&this.audioRef.current){console.log("🎵 Attempting to load audio stream:",n);const r=this.audioRef.current;this.setState({audioState:"loading"}),r.addEventListener("loadstart",this.handleLoadStart),r.addEventListener("playing",this.handlePlaying),r.addEventListener("ended",this.handleEnded);try{await new Promise((s,o)=>{const l=setTimeout(()=>o(new Error("Audio load timeout")),1e4);r.oncanplay=()=>{clearTimeout(l),s(!0)},r.onerror=a=>{var u,c,f;clearTimeout(l),console.error("❌ Audio element error event:",a),console.error(" Error code:",(u=r.error)==null?void 0:u.code),console.error(" Error message:",(c=r.error)==null?void 0:c.message),console.error(" Network state:",r.networkState),console.error(" Ready state:",r.readyState),o(new Error(`Audio load error: ${((f=r.error)==null?void 0:f.message)||"Unknown error"}`))}}),r.muted=!0,await r.play(),console.log("✅ Audio stream is playing (initially muted)"),await new Promise(s=>setTimeout(s,100));const i=r;if(typeof i.captureStream=="function"){const s=i.captureStream();this.setState({mediaStream:s}),console.log("✅ Audio stream captured successfully via captureStream()"),r.muted=!1,console.log("🔊 Audio unmuted - you should now hear the sound")}else if(typeof i.mozCaptureStream=="function"){const s=i.mozCaptureStream();this.setState({mediaStream:s}),console.log("✅ Audio stream captured successfully via mozCaptureStream() (Firefox)"),r.muted=!1,console.log("🔊 Audio unmuted - you should now hear the sound")}else console.error("❌ HTMLAudioElement.captureStream() is not supported in this browser."),r.muted=!1,console.warn("⚠️ Audio unmuted but visualization will use demo mode")}catch(i){console.error("❌ Error playing or capturing audio stream:",i),console.warn("⚠️ Audio stream failed. Not falling back to microphone. Check console for errors.")}}else console.log("🎭 No audio stream provided - running in demo mode (using fake audio data)");$e.setFrameHeight()});Re(this,"componentWillUnmount",()=>{if(this.state.mediaStream&&!this.props.args.streamUrl&&this.state.mediaStream.getTracks().forEach(n=>n.stop()),this.audioRef.current){const n=this.audioRef.current;n.pause(),n.removeEventListener("loadstart",this.handleLoadStart),n.removeEventListener("playing",this.handlePlaying),n.removeEventListener("ended",this.handleEnded)}});Re(this,"componentDidUpdate",async n=>{const r=n.args.streamUrl,i=this.props.args.streamUrl,s=r!==i;if(i&&this.audioRef.current&&(s||this.state.audioState==="ended")){console.log("🔄 Reloading audio:",s?"URL changed":"Replay same URL"),this.setState({mediaStream:null,audioState:"loading"});const l=this.audioRef.current;l.removeEventListener("loadstart",this.handleLoadStart),l.removeEventListener("playing",this.handlePlaying),l.removeEventListener("ended",this.handleEnded),l.addEventListener("loadstart",this.handleLoadStart),l.addEventListener("playing",this.handlePlaying),l.addEventListener("ended",this.handleEnded),s?l.src=i:l.load();try{l.muted=!0,await l.play();const a=l;if(typeof a.captureStream=="function"){const u=a.captureStream();this.setState({mediaStream:u}),console.log("✅ Audio stream updated and captured successfully"),l.muted=!1,console.log("🔊 Audio unmuted")}else if(typeof a.mozCaptureStream=="function"){const u=a.mozCaptureStream();this.setState({mediaStream:u}),console.log("✅ Audio stream updated and captured successfully (Firefox)"),l.muted=!1,console.log("🔊 Audio unmuted")}else l.muted=!1,console.warn("⚠️ captureStream not supported, audio unmuted but using demo visualization")}catch(a){console.error("❌ Error updating audio stream:",a)}}$e.setFrameHeight()});Re(this,"handleLoadStart",()=>{console.log("📥 Audio loadstart"),this.setState({audioState:"loading"})});Re(this,"handlePlaying",()=>{console.log("▶️ Audio playing"),this.setState({audioState:"playing"})});Re(this,"handleEnded",()=>{console.log("⏹️ Audio ended"),this.setState({audioState:"ended"})})}}const q1=$1(Z1);zv.render(Un.jsx(fr.StrictMode,{children:Un.jsx(q1,{})}),document.getElementById("root"));
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: streamlit-bar-visualizer
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.4
|
4
4
|
Summary: Beautiful audio frequency visualizer component for Streamlit with multiple state animations
|
5
5
|
Home-page: https://github.com/bensonbs/streamlit-bar-visualizer
|
6
6
|
Author: Benson Sung
|
@@ -35,24 +35,46 @@ pip install streamlit-bar-visualizer
|
|
35
35
|
## Quick Start
|
36
36
|
|
37
37
|
```python
|
38
|
+
import streamlit as st
|
38
39
|
from streamlit_bar_visualizer import bar_visualizer
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
#
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
40
|
+
import time
|
41
|
+
|
42
|
+
st.title("🎵 Audio Stream Visualizer Test")
|
43
|
+
|
44
|
+
# Session state
|
45
|
+
if 'play_count' not in st.session_state:
|
46
|
+
st.session_state.play_count = 0
|
47
|
+
|
48
|
+
# Audio stream options
|
49
|
+
stream_options = {
|
50
|
+
"Radio Paradise (MP3)": "https://stream.radioparadise.com/mp3-128",
|
51
|
+
"BBC World Service": "https://stream.live.vc.bbcmedia.co.uk/bbc_world_service",
|
52
|
+
"Custom URL": "custom"
|
53
|
+
}
|
54
|
+
|
55
|
+
selected_stream = st.selectbox("Select Audio Stream", list(stream_options.keys()))
|
56
|
+
|
57
|
+
if selected_stream == "Custom URL":
|
58
|
+
stream_url = st.text_input("Enter Audio Stream URL", value="https://stream.radioparadise.com/mp3-128")
|
59
|
+
else:
|
60
|
+
stream_url = stream_options[selected_stream]
|
61
|
+
|
62
|
+
st.info(f"🎧 Current Audio Stream: {stream_url}")
|
63
|
+
|
64
|
+
# Display visualizer
|
65
|
+
if stream_url:
|
66
|
+
bar_visualizer(
|
67
|
+
state="auto",
|
68
|
+
stream_url=stream_url,
|
69
|
+
key=f"visualizer_{st.session_state.play_count}"
|
70
|
+
)
|
71
|
+
|
72
|
+
st.markdown("---")
|
73
|
+
st.caption("💡 Tip: If you don't hear sound, check the browser console (F12) for error messages and verify the audio stream URL is valid.")
|
54
74
|
```
|
55
75
|
|
76
|
+
> ⚠️ **Browser Compatibility Warning**: Safari may have issues with audio streaming due to CORS restrictions. For best results, use **Chrome** or **Firefox**.
|
77
|
+
|
56
78
|
## API
|
57
79
|
|
58
80
|
### `bar_visualizer(state, stream_url, key)`
|
@@ -1,69 +0,0 @@
|
|
1
|
-
# Streamlit Bar Visualizer
|
2
|
-
|
3
|
-
Audio frequency visualizer component for Streamlit, inspired by ElevenLabs UI design.
|
4
|
-
|
5
|
-

|
6
|
-

|
7
|
-
|
8
|
-
## Installation
|
9
|
-
|
10
|
-
```bash
|
11
|
-
pip install streamlit-bar-visualizer
|
12
|
-
```
|
13
|
-
|
14
|
-
## Quick Start
|
15
|
-
|
16
|
-
```python
|
17
|
-
from streamlit_bar_visualizer import bar_visualizer
|
18
|
-
|
19
|
-
# Basic usage
|
20
|
-
bar_visualizer(state="listening")
|
21
|
-
|
22
|
-
# With audio stream
|
23
|
-
bar_visualizer(
|
24
|
-
state="speaking",
|
25
|
-
stream_url="https://stream.live.vc.bbcmedia.co.uk/bbc_world_service"
|
26
|
-
)
|
27
|
-
|
28
|
-
# Auto mode - automatically changes state based on audio playback
|
29
|
-
bar_visualizer(
|
30
|
-
state="auto",
|
31
|
-
stream_url="https://stream.live.vc.bbcmedia.co.uk/bbc_world_service"
|
32
|
-
)
|
33
|
-
```
|
34
|
-
|
35
|
-
## API
|
36
|
-
|
37
|
-
### `bar_visualizer(state, stream_url, key)`
|
38
|
-
|
39
|
-
**Parameters:**
|
40
|
-
- `state` (str): Animation state
|
41
|
-
- `"listening"` - Breathing animation
|
42
|
-
- `"speaking"` - Active speaking animation
|
43
|
-
- `"thinking"` - Pulsing animation
|
44
|
-
- `"connecting"` - Wave animation
|
45
|
-
- `"initializing"` - Building up animation
|
46
|
-
- `"auto"` - Auto-switch based on audio playback (thinking → speaking → initializing)
|
47
|
-
- `stream_url` (str, optional): Audio stream URL to visualize
|
48
|
-
- `key` (str, optional): Unique component identifier
|
49
|
-
|
50
|
-
**Returns:** Current component state (str)
|
51
|
-
|
52
|
-
## Development
|
53
|
-
|
54
|
-
```bash
|
55
|
-
# Install dependencies
|
56
|
-
cd streamlit_bar_visualizer/frontend
|
57
|
-
npm install
|
58
|
-
|
59
|
-
# Start dev server
|
60
|
-
npm run start
|
61
|
-
|
62
|
-
# Build for production
|
63
|
-
npm run build
|
64
|
-
```
|
65
|
-
|
66
|
-
## License
|
67
|
-
|
68
|
-
MIT
|
69
|
-
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|