Ivan000 commited on
Commit
27630e2
·
verified ·
1 Parent(s): 37f1aec

Upload 5 files

Browse files
Files changed (5) hide show
  1. audioPlayer.js +38 -0
  2. index.html +34 -0
  3. main.js +53 -0
  4. styles.css +158 -0
  5. visualizer.js +104 -0
audioPlayer.js ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export class AudioPlayer {
2
+ constructor(audioContext, url) {
3
+ this.audioContext = audioContext;
4
+ this.audio = new Audio();
5
+ this.audio.crossOrigin = 'anonymous';
6
+ this.audio.src = url;
7
+
8
+ this.gainNode = this.audioContext.createGain();
9
+ this.analyser = this.audioContext.createAnalyser();
10
+
11
+ this.source = this.audioContext.createMediaElementSource(this.audio);
12
+ this.source.connect(this.analyser);
13
+ this.analyser.connect(this.gainNode);
14
+ this.gainNode.connect(this.audioContext.destination);
15
+
16
+ this.isPlaying = false;
17
+
18
+ // Set fixed volume
19
+ this.gainNode.gain.value = 0.5;
20
+ }
21
+
22
+ play() {
23
+ this.audioContext.resume();
24
+ this.audio.play();
25
+ this.isPlaying = true;
26
+ }
27
+
28
+ pause() {
29
+ this.audio.pause();
30
+ this.isPlaying = false;
31
+ }
32
+
33
+ connectAnalyser(analyser) {
34
+ this.source.disconnect(this.analyser);
35
+ this.source.connect(analyser);
36
+ analyser.connect(this.gainNode);
37
+ }
38
+ }
index.html ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <html lang="en">
2
+ <head>
3
+ <meta charset="UTF-8">
4
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
5
+ <title>Lofi Radio Player</title>
6
+ <link rel="stylesheet" href="css/styles.css">
7
+ </head>
8
+ <body>
9
+ <div class="player-container">
10
+ <div class="player-card">
11
+ <div class="visualizer-container">
12
+ <canvas id="visualizer"></canvas>
13
+ </div>
14
+
15
+ <div class="controls">
16
+ <div class="play-button" id="playButton">
17
+ <svg class="play-icon" viewBox="0 0 24 24">
18
+ <path d="M8 5v14l11-7z"/>
19
+ </svg>
20
+ <svg class="pause-icon" viewBox="0 0 24 24">
21
+ <path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/>
22
+ </svg>
23
+ </div>
24
+ </div>
25
+
26
+ <div class="track-info">
27
+ <span class="station-name">Lofi Radio</span>
28
+ </div>
29
+ </div>
30
+ </div>
31
+
32
+ <script type="module" src="js/main.js"></script>
33
+ </body>
34
+ </html>
main.js ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { AudioPlayer } from './audioPlayer.js';
2
+ import { Visualizer } from './visualizer.js';
3
+
4
+ const audioContext = new (window.AudioContext || window.webkitAudioContext)();
5
+
6
+ const audioPlayer = new AudioPlayer(audioContext, 'http://stream-174.zeno.fm/c58ssug3668uv?zt=eyJhbGciOiJIUzI1NiJ9.eyJzdHJlYW0iOiJjNThzc3VnMzY2OHV2IiwiaG9zdCI6InN0cmVhbS0xNzQuemVuby5mbSIsInJ0dGwiOjUsImp0aSI6IjY0U1pWa1RFVGJ5aS16VVJjZUE0c2ciLCJpYXQiOjE3MzkyNzc4NzQsImV4cCI6MTczOTI3NzkzNH0.fOQetCgM5m28EmqCIxLYh0hkPzetEDO8ixqTH0cNOw0');
7
+ const visualizer = new Visualizer(audioContext, 'visualizer');
8
+
9
+ audioPlayer.connectAnalyser(visualizer.analyser);
10
+
11
+ const playButton = document.getElementById('playButton');
12
+ const playIcon = playButton.querySelector('.play-icon');
13
+ const pauseIcon = playButton.querySelector('.pause-icon');
14
+
15
+ playButton.addEventListener('click', () => {
16
+ if (audioPlayer.isPlaying) {
17
+ audioPlayer.pause();
18
+ playIcon.style.display = 'block';
19
+ pauseIcon.style.display = 'none';
20
+ } else {
21
+ audioPlayer.play();
22
+ playIcon.style.display = 'none';
23
+ pauseIcon.style.display = 'block';
24
+ }
25
+ });
26
+
27
+ function lerp(start, end, amt) {
28
+ return (1 - amt) * start + amt * end;
29
+ }
30
+
31
+ function updateBackgroundColor(amplitude) {
32
+ // Convert hex colors to RGB
33
+ const startColor = {
34
+ r: 0x1f, g: 0x03, b: 0x6b
35
+ };
36
+ const endColor = {
37
+ r: 0x6a, g: 0x4a, b: 0xc2
38
+ };
39
+
40
+ // Interpolate between colors based on amplitude
41
+ const r = Math.round(lerp(startColor.r, endColor.r, amplitude));
42
+ const g = Math.round(lerp(startColor.g, endColor.g, amplitude));
43
+ const b = Math.round(lerp(startColor.b, endColor.b, amplitude));
44
+
45
+ document.body.style.backgroundColor = `rgb(${r}, ${g}, ${b})`;
46
+ }
47
+
48
+ function animate() {
49
+ const amplitude = visualizer.draw();
50
+ updateBackgroundColor(amplitude);
51
+ requestAnimationFrame(animate);
52
+ }
53
+ animate();
styles.css ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Global Styles */
2
+ :root {
3
+ --primary-bg: #1f036b;
4
+ --card-bg: rgba(37, 41, 66, 0.3);
5
+ --accent-color: rgba(108, 99, 255, 0.8);
6
+ --text-color: rgba(255, 255, 255, 0.9);
7
+ --shadow-color: rgba(0, 0, 0, 0.2);
8
+ --glass-border: rgba(255, 255, 255, 0.1);
9
+ --glass-highlight: rgba(255, 255, 255, 0.07);
10
+ }
11
+
12
+ * {
13
+ margin: 0;
14
+ padding: 0;
15
+ box-sizing: border-box;
16
+ }
17
+
18
+ body {
19
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
20
+ background-color: var(--primary-bg);
21
+ display: flex;
22
+ justify-content: center;
23
+ align-items: center;
24
+ min-height: 100vh;
25
+ color: var(--text-color);
26
+ transition: background-color 0.3s ease;
27
+ }
28
+
29
+ .player-container {
30
+ width: 100%;
31
+ max-width: 400px;
32
+ padding: 20px;
33
+ }
34
+
35
+ .player-card {
36
+ background: var(--card-bg);
37
+ backdrop-filter: blur(12px);
38
+ -webkit-backdrop-filter: blur(12px);
39
+ border-radius: 30px;
40
+ padding: 25px;
41
+ box-shadow:
42
+ 0 10px 30px var(--shadow-color),
43
+ inset 0 1px 1px var(--glass-highlight),
44
+ 0 0 0 1px var(--glass-border);
45
+ position: relative;
46
+ overflow: hidden;
47
+ }
48
+
49
+ .player-card::before {
50
+ content: '';
51
+ position: absolute;
52
+ top: 0;
53
+ left: -50%;
54
+ width: 100%;
55
+ height: 100%;
56
+ background: linear-gradient(
57
+ 90deg,
58
+ transparent,
59
+ var(--glass-highlight),
60
+ transparent
61
+ );
62
+ transform: skewX(-15deg);
63
+ pointer-events: none;
64
+ }
65
+
66
+ .visualizer-container {
67
+ width: 100%;
68
+ height: 200px;
69
+ margin-bottom: 25px;
70
+ border-radius: 20px;
71
+ overflow: hidden;
72
+ background-color: rgba(0, 0, 0, 0.15);
73
+ box-shadow:
74
+ inset 0 2px 5px rgba(0, 0, 0, 0.2),
75
+ 0 0 0 1px var(--glass-border);
76
+ position: relative;
77
+ display: flex;
78
+ align-items: center;
79
+ justify-content: center;
80
+ }
81
+
82
+ canvas {
83
+ width: 100%;
84
+ height: 100%;
85
+ position: absolute;
86
+ left: 0;
87
+ top: 0;
88
+ }
89
+
90
+ .controls {
91
+ display: flex;
92
+ align-items: center;
93
+ justify-content: center;
94
+ margin-bottom: 25px;
95
+ position: relative;
96
+ z-index: 1;
97
+ }
98
+
99
+ .play-button {
100
+ width: 65px;
101
+ height: 65px;
102
+ border-radius: 50%;
103
+ background: var(--accent-color);
104
+ backdrop-filter: blur(5px);
105
+ -webkit-backdrop-filter: blur(5px);
106
+ display: flex;
107
+ align-items: center;
108
+ justify-content: center;
109
+ cursor: pointer;
110
+ transition: all 0.3s ease;
111
+ box-shadow:
112
+ 0 5px 15px rgba(108, 99, 255, 0.3),
113
+ inset 0 1px 1px rgba(255, 255, 255, 0.2);
114
+ border: 1px solid rgba(108, 99, 255, 0.4);
115
+ }
116
+
117
+ .play-button:hover {
118
+ transform: scale(1.05);
119
+ background: rgba(108, 99, 255, 0.9);
120
+ box-shadow:
121
+ 0 8px 20px rgba(108, 99, 255, 0.4),
122
+ inset 0 1px 1px rgba(255, 255, 255, 0.3);
123
+ }
124
+
125
+ .play-button:active {
126
+ transform: scale(0.98);
127
+ }
128
+
129
+ .play-button svg {
130
+ width: 30px;
131
+ height: 30px;
132
+ fill: var(--text-color);
133
+ filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.2));
134
+ }
135
+
136
+ .pause-icon {
137
+ display: none;
138
+ }
139
+
140
+ .track-info {
141
+ text-align: center;
142
+ position: relative;
143
+ z-index: 1;
144
+ }
145
+
146
+ .station-name {
147
+ font-size: 1.2em;
148
+ font-weight: 600;
149
+ display: block;
150
+ text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
151
+ letter-spacing: 0.5px;
152
+ background: linear-gradient(to bottom,
153
+ rgba(255, 255, 255, 0.95),
154
+ rgba(255, 255, 255, 0.8)
155
+ );
156
+ -webkit-background-clip: text;
157
+ -webkit-text-fill-color: transparent;
158
+ }
visualizer.js ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export class Visualizer {
2
+ constructor(audioContext, canvasId) {
3
+ this.canvas = document.getElementById(canvasId);
4
+ this.ctx = this.canvas.getContext('2d');
5
+ this.analyser = audioContext.createAnalyser();
6
+
7
+ // Ensure fftSize is a valid power of two (16384 is the max standard value)
8
+ this.analyser.fftSize = 16384;
9
+ this.bufferLength = this.analyser.frequencyBinCount;
10
+ this.dataArray = new Uint8Array(this.bufferLength);
11
+
12
+ this.sampleRate = audioContext.sampleRate;
13
+ this.binWidth = this.sampleRate / this.analyser.fftSize;
14
+
15
+ // Adjusted frequency range
16
+ this.startBin = Math.floor(20 / this.binWidth);
17
+ this.endBin = Math.floor(200 / this.binWidth);
18
+ this.usableBins = this.endBin - this.startBin;
19
+
20
+ // Slightly reduced smoothing for faster response
21
+ this.analyser.smoothingTimeConstant = 0.45; // Reduced from 0.5
22
+
23
+ // Peak tracking with slightly more dynamic range
24
+ this.peakLevels = new Array(this.usableBins).fill(0);
25
+ this.peakDecayRate = 0.85; // Slightly faster decay
26
+ this.dynamicPeakMultiplier = 1.25; // Increased from 1.2
27
+
28
+ this.resize();
29
+ window.addEventListener('resize', () => this.resize());
30
+ }
31
+
32
+ resize() {
33
+ const dpr = window.devicePixelRatio || 1;
34
+ this.canvas.width = this.canvas.offsetWidth * dpr;
35
+ this.canvas.height = this.canvas.offsetHeight * dpr;
36
+ this.ctx.scale(dpr, dpr);
37
+ this.canvas.style.width = this.canvas.offsetWidth + 'px';
38
+ this.canvas.style.height = this.canvas.offsetHeight + 'px';
39
+ }
40
+
41
+ getAverageAmplitude() {
42
+ let sum = 0;
43
+ for (let i = this.startBin; i < this.endBin; i++) {
44
+ sum += this.dataArray[i];
45
+ }
46
+ // Slightly increased sensitivity
47
+ return Math.min((sum / this.usableBins / 255) * 0.75, 1);
48
+ }
49
+
50
+ draw() {
51
+ this.analyser.getByteFrequencyData(this.dataArray);
52
+
53
+ const width = this.canvas.offsetWidth;
54
+ const height = this.canvas.offsetHeight;
55
+
56
+ // Slightly more transparent fade effect with a color closer to the background
57
+ this.ctx.fillStyle = 'rgba(31, 3, 107, 0.3)';
58
+ this.ctx.fillRect(0, 0, width, height);
59
+
60
+ const barWidth = (width / this.usableBins) * 0.9;
61
+ const spacing = barWidth * 0.1;
62
+ let x = (width - (this.usableBins * (barWidth + spacing))) / 2;
63
+
64
+ for (let i = this.startBin; i < this.endBin; i++) {
65
+ const index = i - this.startBin;
66
+ const currentValue = this.dataArray[i];
67
+
68
+ // More controlled peak tracking
69
+ this.peakLevels[index] = Math.max(
70
+ currentValue,
71
+ Math.min(
72
+ this.peakLevels[index] * this.peakDecayRate,
73
+ currentValue * this.dynamicPeakMultiplier
74
+ )
75
+ );
76
+
77
+ // Slightly reduced peak height scaling
78
+ const barHeight = Math.min(
79
+ (this.peakLevels[index] * 0.9) * (height / 255),
80
+ height
81
+ );
82
+
83
+ const gradient = this.ctx.createLinearGradient(0, height, 0, height - barHeight);
84
+ gradient.addColorStop(0, 'rgba(106, 74, 194, 0.8)'); // Matching the end color of background
85
+ gradient.addColorStop(1, 'rgba(31, 3, 107, 0.4)'); // Matching the start color of background
86
+
87
+ this.ctx.fillStyle = gradient;
88
+
89
+ this.ctx.beginPath();
90
+ this.ctx.roundRect(
91
+ x,
92
+ height - barHeight,
93
+ barWidth,
94
+ barHeight,
95
+ 3
96
+ );
97
+ this.ctx.fill();
98
+
99
+ x += barWidth + spacing;
100
+ }
101
+
102
+ return this.getAverageAmplitude();
103
+ }
104
+ }