streamlit-bar-visualizer 0.1.2__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.
Files changed (17) hide show
  1. {streamlit_bar_visualizer-0.1.2/streamlit_bar_visualizer.egg-info → streamlit_bar_visualizer-0.1.4}/PKG-INFO +38 -16
  2. streamlit_bar_visualizer-0.1.4/README.md +91 -0
  3. {streamlit_bar_visualizer-0.1.2 → streamlit_bar_visualizer-0.1.4}/pyproject.toml +1 -1
  4. {streamlit_bar_visualizer-0.1.2 → streamlit_bar_visualizer-0.1.4}/setup.py +1 -1
  5. {streamlit_bar_visualizer-0.1.2 → streamlit_bar_visualizer-0.1.4}/streamlit_bar_visualizer/frontend/build/static/js/index.js +1 -1
  6. {streamlit_bar_visualizer-0.1.2 → streamlit_bar_visualizer-0.1.4/streamlit_bar_visualizer.egg-info}/PKG-INFO +38 -16
  7. streamlit_bar_visualizer-0.1.2/README.md +0 -69
  8. {streamlit_bar_visualizer-0.1.2 → streamlit_bar_visualizer-0.1.4}/LICENSE +0 -0
  9. {streamlit_bar_visualizer-0.1.2 → streamlit_bar_visualizer-0.1.4}/MANIFEST.in +0 -0
  10. {streamlit_bar_visualizer-0.1.2 → streamlit_bar_visualizer-0.1.4}/setup.cfg +0 -0
  11. {streamlit_bar_visualizer-0.1.2 → streamlit_bar_visualizer-0.1.4}/streamlit_bar_visualizer/__init__.py +0 -0
  12. {streamlit_bar_visualizer-0.1.2 → streamlit_bar_visualizer-0.1.4}/streamlit_bar_visualizer/frontend/build/index.html +0 -0
  13. {streamlit_bar_visualizer-0.1.2 → streamlit_bar_visualizer-0.1.4}/streamlit_bar_visualizer/frontend/build/static/css/index.css +0 -0
  14. {streamlit_bar_visualizer-0.1.2 → streamlit_bar_visualizer-0.1.4}/streamlit_bar_visualizer.egg-info/SOURCES.txt +0 -0
  15. {streamlit_bar_visualizer-0.1.2 → streamlit_bar_visualizer-0.1.4}/streamlit_bar_visualizer.egg-info/dependency_links.txt +0 -0
  16. {streamlit_bar_visualizer-0.1.2 → streamlit_bar_visualizer-0.1.4}/streamlit_bar_visualizer.egg-info/requires.txt +0 -0
  17. {streamlit_bar_visualizer-0.1.2 → 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.2
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
- # Basic usage
41
- bar_visualizer(state="listening")
42
-
43
- # With audio stream
44
- bar_visualizer(
45
- state="speaking",
46
- stream_url="https://stream.live.vc.bbcmedia.co.uk/bbc_world_service"
47
- )
48
-
49
- # Auto mode - automatically changes state based on audio playback
50
- bar_visualizer(
51
- state="auto",
52
- stream_url="https://stream.live.vc.bbcmedia.co.uk/bbc_world_service"
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
+ ![License](https://img.shields.io/badge/license-MIT-blue.svg)
6
+ ![Python](https://img.shields.io/badge/python-3.8+-blue.svg)
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.2"
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.2",
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;if(i&&r!==i&&this.audioRef.current){console.log("🔄 Stream URL changed, updating audio source..."),this.setState({mediaStream:null,audioState:"loading"});const s=this.audioRef.current;s.removeEventListener("loadstart",this.handleLoadStart),s.removeEventListener("playing",this.handlePlaying),s.removeEventListener("ended",this.handleEnded),s.addEventListener("loadstart",this.handleLoadStart),s.addEventListener("playing",this.handlePlaying),s.addEventListener("ended",this.handleEnded),s.src=i;try{s.muted=!0,await s.play();const o=s;if(typeof o.captureStream=="function"){const l=o.captureStream();this.setState({mediaStream:l}),console.log("✅ Audio stream updated and captured successfully"),s.muted=!1,console.log("🔊 Audio unmuted")}else if(typeof o.mozCaptureStream=="function"){const l=o.mozCaptureStream();this.setState({mediaStream:l}),console.log("✅ Audio stream updated and captured successfully (Firefox)"),s.muted=!1,console.log("🔊 Audio unmuted")}}catch(o){console.error("❌ Error updating audio stream:",o)}}$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.2
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
- # Basic usage
41
- bar_visualizer(state="listening")
42
-
43
- # With audio stream
44
- bar_visualizer(
45
- state="speaking",
46
- stream_url="https://stream.live.vc.bbcmedia.co.uk/bbc_world_service"
47
- )
48
-
49
- # Auto mode - automatically changes state based on audio playback
50
- bar_visualizer(
51
- state="auto",
52
- stream_url="https://stream.live.vc.bbcmedia.co.uk/bbc_world_service"
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
- ![License](https://img.shields.io/badge/license-MIT-blue.svg)
6
- ![Python](https://img.shields.io/badge/python-3.8+-blue.svg)
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
-