PageSourceSearch

http://www.9virtues.com/npublic/commonjs/nouglify/audioPlay/webPlayer.js

js 9virtues.com collected 2026-10-04 13:23:56 UTC 14,952 bytes, 266 lines download raw bytes

1(function (root, factory) {
2	if (typeof define === 'function' && define.amd) {
3		define([], factory);
4	} else if (typeof module === 'object' && module.exports) {
5		module.exports = factory();
6	} else {
7		root.webPlayer = factory();
8	}
9})(this, function () {
10	'use strict';
11    class webPlayer {
12        constructor(option) {
13            this.option = $.extend({
14                element:"",
15            }, option);
16            this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
17            this.sourceNode = null;
18            this.gainNode = this.audioContext.createGain();
19            this.audioBuffer = null;
20            this.startTime = 0;
21            this.pauseTime = 0;
22            this.isPlaying = false;
23            this._onEnded = this._handleEnded.bind(this);
24            this.playbackRate = 1.0; // 默认正常速度
25            this.volume = 1.0; // 默认音量
26            this.playlist = [];
27            this.currentIndex = 0;
28            this.onTrackEnd = null;
29            this.onPlaylistEnd = null;
30            this.onTrackError = null;
31            this.AudioElement = this.option.element
32        }
33        async loadAudio(url) {
34            const response = await fetch(url);
35            const arrayBuffer = await response.arrayBuffer();
36            this.audioBuffer = await this.audioContext.decodeAudioData(arrayBuffer);
37        }
38        _handleEnded() {
39            if (this.isPlaying) {
40                this.isPlaying = false;
41                this.pauseTime = 0;
42                this.sourceNode = null;
43                
44                if (this.onTrackEnd) {
45                    this.onTrackEnd(this.playlist[this.currentIndex]);
46                }
47               
48                this.currentIndex++;
49                if (this.currentIndex < this.playlist.length) {
50                    this.loadAndPlay(this.playlist[this.currentIndex]);
51                } else if (this.onPlaylistEnd) {
52                    this.hideControl()
53                    this.onPlaylistEnd();
54                }
55            }
56        }
57        async loadAndPlay(url) {
58            try {
59                await this.loadAudio(url);
60                this.play();
61            } catch (error) {
62                console.error(`加载 ${url} 失败:`, error);
63                // 即使失败也继续播放下一个
64                this._handleEnded();
65            }
66        }
67        setPlaylist(tracks, startIndex = 0) {
68            this.playlist = tracks;
69            this.currentIndex = startIndex;
70        }
71        startPlaylist() {
72            if (this.playlist.length > 0) {
73                this.currentIndex = 0;
74                this.loadAndPlay(this.playlist[0]);
75                this.renderControl()
76            }
77        }
78        play() {
79            if (!this.audioBuffer) return;
80            if(this.element){
81                this.element.addClass('isPlay')
82            }
83            this.stopOtherSourcePlayer()
84            // 如果已暂停,从暂停位置继续
85            const offset = this.pauseTime || 0;
86            // 创建新节点
87            this.sourceNode = this.audioContext.createBufferSource();
88            this.sourceNode.buffer = this.audioBuffer;
89            // 连接节点:源 → 增益 → 输出
90            this.sourceNode.connect(this.gainNode);
91            this.gainNode.connect(this.audioContext.destination);
92
93            // 设置播放速度
94            this.sourceNode.playbackRate.value = this.playbackRate;
95
96            // this.sourceNode.connect(this.audioContext.destination);
97            // 设置结束回调(注意不要用匿名函数,方便移除)
98            this.sourceNode.onended = this._onEnded;
99            this.startTime = this.audioContext.currentTime - offset;
100            this.sourceNode.start(0, offset);
101            this.isPlaying = true;
102            this.pauseTime = 0;
103            
104        }
105        pause() {
106            if (!this.isPlaying) return;
107            if(this.element){
108                this.element.removeClass('isPlay')
109            }
110            // 临时移除结束回调
111            this.sourceNode.onended = null;
112            // 停止播放
113            this.sourceNode.stop();
114            this.sourceNode = null;
115            // 记录暂停位置
116            this.pauseTime = this.audioContext.currentTime - this.startTime;
117            this.isPlaying = false;
118            console.log('已暂停,位置:', this.pauseTime);
119        }
120        stop() {
121            if (this.sourceNode) {
122                // 临时移除结束回调
123                this.sourceNode.onended = null;
124                this.sourceNode.stop();
125                this.sourceNode = null;
126            }
127            this.isPlaying = false;
128            this.pauseTime = 0;
129        }
130        togglePlayPause() {
131            if (this.isPlaying) {
132                this.pause();
133            } else {
134                this.play();
135            }
136        }
137        setSpeed(rate) {
138            if(this.element){
139                let citem = this.element.find('.speedItem[data-value="'+rate+'"]')
140                citem.addClass('active').siblings().removeClass('active')
141                this.element.find('.sppedBtn').text(rate+'x')
142                this.element.find('.speedBar').removeClass('isShow')
143            }
144            this.playbackRate = rate;
145            
146            // 如果正在播放,立即应用新速度
147            if (this.sourceNode) {
148              this.sourceNode.playbackRate.value = rate;
149            }
150        }
151        setVolumn(value) {
152            if(this.element){
153                if(value==0){
154                    this.element.addClass('isMuted')
155                }else{
156                    this.element.removeClass('isMuted')
157                }
158                this.element.find('.volumnBar input').val(value)
159            }
160            this.volume = value;
161            
162            // 如果正在播放,立即应用新速度
163            if (this.gainNode) {
164                this.gainNode.gain.value = value;
165            }
166        }
167        // 其他实用方法
168        get currentTime() {
169            if (this.isPlaying) {
170                return this.audioContext.currentTime - this.startTime;
171            }
172            return this.pauseTime || 0;
173        }
174        get duration() {
175            return this.audioBuffer ? this.audioBuffer.duration : 0;
176        }
177        // 销毁时释放资源
178        dispose() {
179            this.stop();
180            if (this.audioContext.state !== 'closed') {
181                this.audioContext.close();
182            }
183        }
184        renderCss(){
185            if($('#AudioPlayCss').length>0){
186                return
187            }
188            let css = `.AudioPlay{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);border-radius:6px;box-shadow:0 0 20px rgb(0 0 0 / 10%);background-color:#fff;padding:10px;display:flex;color:#666;font-size:12px;z-index:99;}.AudioPlay .opbtn{display:block;height:32px;line-height:20px;min-width:32px;padding:6px;text-align:center;position:relative;border-radius:4px;transition:all .3s;}.AudioPlay .opbtn svg{display:block;width:20px;height:20px;margin:0;fill:currentColor;}.AudioPlay a:hover{color:#fff;background-color:#409EFF}.AudioPlay .pause,.AudioPlay.isPlay .play,.AudioPlay .volumnClose,.AudioPlay.isMuted .volumnOpen{display:none;}.AudioPlay .play,.AudioPlay.isPlay .pause,.AudioPlay .volumnOpen,.AudioPlay.isMuted .volumnClose{display:block;}.AudioPlay .volumnCon{display:flex;align-items:center;}.AudioPlay .volumnBar{width:90px;height:16px;padding:0 5px;display:flex;align-items:center;}.AudioPlay .volumnBar input{height:4px;width:100%;}.AudioPlay .speedCon{position:relative;}.AudioPlay .speedBar{padding:5px;border-radius:4px;position:absolute;width:80px;background-color:#fff;bottom:50px;right:0;box-shadow:0 0 20px rgb(0 0 0 / 10%);display:none;}.AudioPlay .speedBar.isShow{display:block;}.AudioPlay .speedItem{display:flex;align-items:center;cursor:pointer;padding:5px;border-radius:4px;position:relative;}.AudioPlay .speedItem:before{background:#0000001a;content:"";display:block;flex-shrink:0;height:16px;margin-right:10px;transition:all .3s ease;width:16px;border-radius:50%;}.AudioPlay .speedItem:after{background:#fff;content:"";border:0;height:6px;left:10px;opacity:0;top:50%;transform:translateY(-50%) scale(0);width:6px;position:absolute;border-radius:50%;}.AudioPlay .speedItem.active:before{background:#00b2ff;}.AudioPlay .speedItem.active:after{opacity:1;transform:translateY(-50%) scale(1);}`
189            let style = document.createElement('style');
190            style.type = 'text/css';
191            style.id = 'AudioPlayCss'
192            style.innerHTML = css;
193            document.getElementsByTagName('head')[0].appendChild(style);
194        }
195        renderControl(){
196            this.renderCss();
197            let dom = `<div class="AudioPlay" id="webPlay_${this.option.cid}_${this.option.eid}">
198                <div class="playCon">
199                    <a href="javascript:;" class="opbtn play">
199<svg t="1746497108766" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="https://www.w3.org/2000/svg" p-id="7652"><path d="M324.085 95.787l500.422 300.664c82.373 50.453 79.284 136.946-1.030 186.37v0l-506.6 304.784c-41.187 23.683-87.522 37.068-131.798 9.267-36.037-22.653-46.335-58.691-46.335-97.819v-616.774c0-39.127 13.386-75.166 48.395-97.819 45.305-27.801 94.731-14.416 136.946 11.327v0z" p-id="7653"></path></svg></a>
200                    <a href="javascript:;" class="opbtn pause"><svg t="1745995311394" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="https://www.w3.org/2000/svg" p-id="14135"><path d="M432 176v672c0 26.5-21.5 48-48 48H224c-26.5 0-48-21.5-48-48V176c0-26.5 21.5-48 48-48h160c26.5 0 48 21.5 48 48zM848 176v672c0 26.5-21.5 48-48 48H640c-26.5 0-48-21.5-48-48V176c0-26.5 21.5-48 48-48h160c26.5 0 48 21.5 48 48z" p-id="14136"></path></svg></a>
201                </div>
202                <div class="volumnCon">
203                    <a href="javascript:;" class="opbtn volumnOpen"><svg t="1745995352893" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="https://www.w3.org/2000/svg" p-id="15288"><path d="M473.088 125.44L256 256H52.224C23.552 256 0 279.552 0 308.224V716.8c0 28.16 23.04 51.2 51.2 51.2h204.8l217.088 130.56c16.896 10.24 38.912-2.048 38.912-22.016V147.456c0-19.968-21.504-32.256-38.912-22.016zM699.904 320.512c-20.992-18.944-53.248-17.408-72.192 3.584-18.944 20.992-17.408 53.248 3.584 72.192 0.512 0.512 58.368 54.784 58.368 121.344 0 37.888-19.456 74.752-58.368 110.08-20.992 18.944-22.528 51.2-3.584 72.192 10.24 11.264 24.064 16.896 37.888 16.896 12.288 0 24.576-4.608 34.304-13.312 61.44-55.296 92.16-117.76 92.16-185.856 0-112.64-88.576-193.536-92.16-197.12z" fill="" p-id="15289"></path><path d="M853.504 166.4c-20.992-18.944-53.248-16.896-72.192 4.096-18.944 20.992-16.896 53.248 4.096 72.192 1.536 1.024 135.68 122.88 135.68 280.576 0 90.624-45.568 177.152-135.68 257.536-20.992 18.944-23.04 51.2-4.096 72.192 10.24 11.264 24.064 16.896 38.4 16.896 12.288 0 24.576-4.096 34.304-12.8 112.64-100.864 169.984-212.992 169.984-333.824-1.024-202.752-163.84-350.208-170.496-356.864z" fill="" p-id="15290"></path></svg></a>
204                    <a href="javascript:;" class="opbtn volumnClose"><svg t="1745995429643" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="https://www.w3.org/2000/svg" p-id="16264"><path d="M256 768H51.2c-28.16 0-51.2-23.04-51.2-51.2V308.224C0 279.552 23.552 256 52.224 256H256v512zM512 147.456v728.576c0 19.968-21.504 32.256-38.912 22.016L256 768V256l217.088-130.56c17.408-10.24 38.912 2.048 38.912 22.016zM623.104 656.896c-19.968-19.968-19.968-52.224 0-72.192l217.088-217.088c19.968-19.968 52.224-19.968 72.192 0 19.968 19.968 19.968 52.224 0 72.192l-217.088 217.088c-19.456 19.968-52.224 19.968-72.192 0z" fill="" p-id="16265"></path><path d="M623.104 367.104c19.968-19.968 52.224-19.968 72.192 0l217.088 217.088c19.968 19.968 19.968 52.224 0 72.192-19.968 19.968-52.224 19.968-72.192 0l-217.088-217.088c-19.968-19.456-19.968-52.224 0-72.192z" fill="" p-id="16266"></path></svg></a>
205                    <div class="volumnBar">
206                        <input class="volumnInput" type="range" min="0" max="1" step="0.05" value="1" autocomplete="off" role="slider" />
207                    </div>
208                </div>
209                <div class="speedCon">
210                    <a href="javascript:;" class="opbtn sppedBtn">1x</a>
211                    <div class="speedBar">
212                        <a href="javascript:;" class="speedItem" data-value="0.5"><span>0.5x</span></a>
213                        <a href="javascript:;" class="speedItem" data-value="0.75"><span>0.75x</span></a>
214                        <a href="javascript:;" class="speedItem active" data-value="1.0"><span>1.0x</span></a>
215                        <a href="javascript:;" class="speedItem" data-value="1.25"><span>1.25x</span></a>
216                        <a href="javascript:;" class="speedItem" data-value="1.5"><span>1.5x</span></a>
217                        <a href="javascript:;" class="speedItem" data-value="1.75"><span>1.75x</span></a>
218                        <a href="javascript:;" class="speedItem" data-value="2.0"><span>2.0x</span></a>
219                    </div>
220                </div>
221            </div>`
222            this.playbackRate = 1.0; // 默认正常速度
223            this.volume = 1.0; // 默认音量
224            this.element = $(dom)
225            this.AudioElement.append(this.element)
226            this.element.addClass('isPlay')
227            this.controlEvent()
228        }
229        controlEvent(){
230            this.element.on("click",'.play',()=>{
231                this.play()
232            }).on('click','.pause',()=>{
233                this.pause()
234            }).on('click','.volumnOpen',()=>{
235                this.setVolumn(0)
236            }).on('click','.volumnClose',()=>{
237                this.setVolumn(1)
238            }).on('input','.volumnInput',(e)=>{
239                let v = $(e.currentTarget).val()
240                this.setVolumn(v)
241            }).on('click','.sppedBtn',()=>{
242                this.element.find('.speedBar').addClass('isShow')
243            }).on('click','.speedItem',(e)=>{
244                let v = $(e.currentTarget).attr('data-value')
245                this.setSpeed(v)
246            })
247            $(document).click((e)=> { 
248                var pop = this.element.find(".speedBar,.speedItem,.sppedBtn")
249                if(!pop.is(e.target) && pop.has(e.target).length === 0) { 
250                    this.element.find('.speedBar').removeClass('isShow')
251                }  
252            }); 
253        }
254        hideControl(){
255            this.element.remove()
256        }
257        stopOtherSourcePlayer(){
258            for (let key in window.sourcePlayer) {
259                if (key != `${this.option.cid}_${this.option.eid}`) {
260                    window.sourcePlayer[key].pause();
261                }
262            }
263        }
264    }
265  	return webPlayer;
266});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.