PageSourceSearch

https://www.yte.co.jp/music/library/js/audio_player.js

js yte.co.jp collected 2026-10-02 17:12:26 UTC 14,351 bytes, 388 lines download raw bytes

1/**
2 * 音源プレイヤー
3 * 以下のライブラリが必要
4 * wavesurfer.js, jquery.js
5 * 
6 * @doc https://wavesurfer-js.org/
7 */
8class AudioPlayer {
9    constructor() {
10        this.el1 = '#waveform1';
11        //this.el2 = '#waveform2';
12        this.player1 = {};
13        this.trackLoadedAt = 0; // 直近に曲を読み込んだ時刻(ms)。読み込み直後の「見せかけの再生終了」判定に使用
14        this.initWaveSurfer();
15        this.initPlayerActions();
16    }
17    /**
18     * 波形表示ライブラリを読み込む
19     *
20     * WebAudioとMediaElementには多くの違いがあるので、以下のドキュメントを読むことを推奨
21     * 
22     * @see https://github.com/katspaugh/wavesurfer.js/issues/676
23     * @see https://github.com/katspaugh/wavesurfer.js/issues/509
24     */
25    initWaveSurfer() {
26        const options1 = {
27          container: this.el1,
28          waveColor: "#E93344", // 波形の未再生部分の色
29          progressColor: "#BA2936", // 波形の再生済みの部分の色
30          backgroundColor: "transparent", // 波形の背景色
31          cursorColor: "#FFFFFF", // シークバー(縦棒)の枠色
32          splitChannels: false,
33          height: 40, // px
34          barWidth: 1, // 値を大きくすると波形が右図のようにブロック上になる: ▁▂▇▃▅▂
35          cursorWidth: 2, // px
36          //backend: 'WebAudio' // 音源ファイルのDLが完了するまで再生されない, iOS 音量調整が使用可能
37          backend: "MediaElement", // 音源ファイルのDLを待たずにストリーミング再生可能, iOS 音量調整が使用不可
38        };
39        const wavesurfer1 = WaveSurfer.create(options1);
40        this.player1 = wavesurfer1;
41    }
42    /**
43     * 制御系の初期化
44     */
45    initPlayerActions() {
46        this.play();
47        //this.pause();
48        this.togglePlay();
49        //this.stop();
50        this.toggleMute();
51        this.adjustVolume();
52        this.showCurrentTime();
53        this.showDurationTime();
54        this.playNextTrack();
55        this.playPrevTrack();
56        this.autoPlayNextTrack();
57    }
58    /**
59     * 波形表示を非表示にする
60     */
61    hideWaveForm() {
62        // $(this.el1).hide();
63        $(this.el1).css("visibility", "hidden");
64    }
65    /**
66     * 波形表示を非表示にする
67     */
68    showWaveForm() {
69        // $(this.el1).show();
70        $(this.el1).css("visibility", "visible");
71    }
72    /**
73     * 曲を読み込む
74     * backend に WebAudio を指定した場合
75     * play メソッドを実行することで初めて読み込まれる
76     * 
77     * @see https://github.com/katspaugh/wavesurfer.js/issues/676
78     */
79    loadTrack(trackPath) {
80        this.trackLoadedAt = performance.now(); // 読み込み時刻を記録(即時finish連鎖の検知に使用)
81        // load() は Promise を返す。曲を連続で切り替えると前の読み込みが中断され、
82        // "AbortError: signal is aborted without reason" が未処理例外として出るため握り潰す
83        this.player1.load(trackPath).catch(() => {});
84        //this.player1.play(); // backend が WebAudio の時、必須。 MediaElement なら不要
85    }
86    /**
87     * 再生(プレイリストのリンクをクリックで再生)
88     */
89    play() {
90        const self = this;
91        //const target = "#btn_play1";
92        const target = "#tracks a.track_link";
93        const links1 = $(target);
94        $(target).on('click', function (e) {
95          e.preventDefault();
96          let trackPath = $(this).attr("href");
97          let trackIndex = links1.index(this);
98          self.setCurrentTrack(trackIndex);
99          self.showWaveForm();
100          self.showTrackInfo(this);
101
102          /*
103          self.player1.on("ready", self.player1.play.bind(self.player1));
104          // 音量調整をしていない場合
105          if (!$("#volume1").hasClass("volume-adjusted")) {
106            self.player1.setVolume(0.4);
107          }
108          */
109
110          // wavesurfer.js 7.9.4対応: readyイベントの処理方法を変更
111          const readyHandler = () => {
112            self.player1.setTime(0); // 前曲の末尾位置の引き継ぎを断ち、必ず先頭から再生する
113            self.player1.play();
114            // 音量調整をしていない場合
115            if (!$("#volume1").hasClass("volume-adjusted")) {
116              self.player1.setVolume(0.4);
117            }
118            // イベントリスナーを一度だけ実行するために削除
119            self.player1.un("ready", readyHandler);
120          };
121
122          // readyイベントリスナーを追加
123          self.player1.on("ready", readyHandler);
124
125          //$('#btn_playpause1').removeClass("pause");
126          $("#btn_playpause1").addClass("pause");
127          $(target).removeClass("now-playing");
128          $(target).find(".track_play").removeClass("pause");
129          $(this).addClass("now-playing");
130          $(this).find(".track_play").addClass("pause");
131          self.changeAlbumInfo();
132        });
133    }
134    /**
135     * 一時停止
136     */
137    pause() {
138        const self = this;
139        const target = "#btn_pause1";
140        $(target).on('click', function () {
141            self.player1.pause();
142        });
143    }
144    /**
145     * 一時停止中なら再生
146     * 再生中なら一時停止
147     */
148    togglePlay() {
149        const self = this;
150        const target = "#btn_playpause1";
151        $(target).on('click', function () {
152            if ($(".now-playing").length === 0) { // 曲を一度も再生せずに押したら処理中止
153                return false;
154            }
155            self.player1.playPause();
156            $('#btn_playpause1').toggleClass("pause");
157            $('.now-playing').find('.track_play').toggleClass('pause');
158        });
159    }
160    /**
161     * 停止して最初に戻る
162     */
163    stop() {
164        const self = this;
165        const target = "#btn_stop1";
166        $(target).on('click', function () {
167            self.player1.stop();
168        });
169    }
170    /**
171     * 消音トグル
172     */
173    toggleMute() {
174        const self = this;
175        const target = "#btn_mute1";
176        $(target).on('click', function () {
177            self.player1.toggleMute();
178        });
179    }
180    /**
181     * 現在再生中の時間を返す 
182     */
183    getCurrentTime() {
184        let raw = this.player1.getCurrentTime();
185        let time = this.formatTime(raw);
186        return time;
187    }
188    /**
189     * 現在再生中の時間を表示する 
190     */
191    showCurrentTime() {
192        const self = this;
193        const target = '#current_time1';
194        self.player1.on('audioprocess', function () {
195            let time = self.getCurrentTime();
196            $(target).text(time);
197        });
198    }
199    /**
200     * 曲の再生終了時間を返す 
201     */
202    getDurationTime() {
203        let raw = this.player1.getDuration();
204        let time = this.formatTime(raw);
205        return time;
206    }
207    /**
208     * 再生終了時間を表示する 
209     */
210    showDurationTime() {
211        const self = this;
212        let target = "#duration_time1";
213        self.player1.on('ready', function () {
214            let time = self.getDurationTime();
215            $(target).text(time);
216        });
217    }
218    /**
219     * 音量調整
220     */
221    adjustVolume() {
222        const self = this;
223        const target = "#volume1";
224        // backend が MediaElement の時に使用。iOSで音量調整が使用できないため、スライダを非表示にする
225        // backend が WebAudio の時はコメントアウトすること。iOSで音量調整が使用できるため
226        // その代わり、ストリーミング再生ができないため、一見、音源が読み込まれていないような誤解を生む
227        if (self.iOS()) {
228            //$(".volume_control_box").css("visibility", "hidden");
229            //return false;
230        }
231        $(target).on('input', function () {
232            if ($(".now-playing").length === 0) { // 曲を一度も再生せずに押したら処理中止
233                return false;
234            }
235            let newVolume = $(this).val();
236            self.player1.setVolume(newVolume);
237            $(this).addClass("volume-adjusted");
238        });
239    }
240    /**
241     * iOS端末かどうかを判定する
242     */
243    iOS() {
244        return [
245            'iPad Simulator',
246            'iPhone Simulator',
247            'iPod Simulator',
248            'iPad',
249            'iPhone',
250            'iPod'
251        ].includes(navigator.platform)
252            // iPad on iOS 13 detection
253            || (navigator.userAgent.includes("Mac") && "ontouchend" in document)
254    }
255    /**
256     * 経過秒を分秒(m:ss)に変換して返す
257     */
258    formatTime(time) {
259        const min = Math.floor((time % 3600) / 60);
260        const sec = ('00' + Math.floor(time % 60)).slice(-2);
261        const formattedTime = [min, sec].join(':');
262        return formattedTime;
263    }
264    /**
265     * Load a track by index and highlight the corresponding link
266     */
267    setCurrentTrack(index) {
268        const links1 = $('#tracks a.track_link');
269        this.current_track_idx = index || 0;
270        //console.log(this.current_track_idx);
271        const trackPath = links1[index].href;
272        this.loadTrack(trackPath);
273    }
274    /**
275     * 次の曲を再生
276     * 最後の曲を再生中に次の曲を再生したら、最初の曲を初めから再生し直す
277     */
278    playNextTrack() {
279        const self = this;
280        const target = "#btn_next1";
281        const links1 = $('#tracks a.track_link');
282        $(target).click(function () {
283            if ($(".now-playing").length === 0) { // 曲を一度も再生せずに押したら処理中止
284                return false;
285            }
286            const currentIndex = self.current_track_idx;
287            let newIndex = (currentIndex + 1) % links1.length;
288            self.setCurrentTrack(newIndex);
289            const elem = $(links1[newIndex]);
290            self.showTrackInfo(elem);
291            //$('#btn_playpause1').removeClass("pause");
292            $('#btn_playpause1').addClass("pause");
293            $(links1).removeClass("now-playing");
294            $(links1).find('.track_play').removeClass('pause');
295            $(elem).addClass('now-playing');
296            $(elem).find('.track_play').addClass('pause');
297            self.changeAlbumInfo();
298        });
299    }
300    /**
301     * 前の曲を再生 
302     * 最初の曲を再生中に前の曲を再生したら、最後の曲を初めから再生し直す
303     */
304    playPrevTrack() {
305        const self = this;
306        const target = "#btn_prev1";
307        const links1 = $('#tracks a.track_link');
308        $(target).click(function () {
309            if ($(".now-playing").length === 0) { // 曲を一度も再生せずに押したら処理中止
310                return false;
311            }
312            const currentIndex = self.current_track_idx;
313            let newIndex = (currentIndex - 1) % links1.length;
314            newIndex = newIndex >= 0 ? newIndex : links1.length - 1;
315            self.setCurrentTrack(newIndex);
316            const elem = $(links1[newIndex]);
317            self.showTrackInfo(elem);
318            //$('#btn_playpause1').removeClass("pause");
319            $('#btn_playpause1').addClass("pause");
320            $(links1).removeClass("now-playing");
321            $(links1).find('.track_play').removeClass('pause');
322            $(elem).addClass('now-playing');
323            $(elem).find('.track_play').addClass('pause');
324            self.changeAlbumInfo();
325        });
326    }
327    /**
328     * 楽曲情報を表示する 
329     */
330    showTrackInfo(elem) {
331        const parent = $(elem).children(".track_box");
332        $("#track_name1").text($(parent).find(".track_track_name").text());
333        $("#artist_name1").text($(parent).find(".track_artist_name").text());
334    }
335    /**
336     * アートワーク画像を変更する
337     */
338    changeAlbumInfo() {
339        const parent = $("section#library .library_head");
340        const target = $("#tracks a.track_link.now-playing");
341        let item = {
342            'track_name': $(target).find(".track_track_name").text(),
343            'artist_name': $(target).find(".track_artist_name").text(),
344            'album_name': $(target).find(".track_img > img").attr("alt"),
345            'album_pic': $(target).find(".track_img > img").attr("src"),
346        };
347        $(parent).find("figure > img").attr("src", item.album_pic).attr("alt", item.album_name);
348        $(parent).find("dl > dd").text(item.artist_name);
349    }
350    /**
351     * 現在の曲の再生が終了したら
352     * 自動で次の曲を再生する
353     * 最後の曲の再生が終了したら、自動再生を終了する
354     */
355    autoPlayNextTrack() {
356        const self = this;
357        const links1 = $('#tracks a.track_link');
358        this.player1.on('finish', function () {
359            // Range対応サーバ(206)では曲切替時に前曲の末尾位置が引き継がれ、
360            // 読み込み直後に即 finish が発火して最後の曲まで連鎖スキップしてしまう。
361            // 実際に再生されていない「見せかけの終了」は無視し、先頭へ戻す。
362            // (全曲30秒。通常再生ならこの閾値を必ず超えるため誤検知しない)
363            const MIN_PLAY_MS = 1500;
364            if (performance.now() - self.trackLoadedAt < MIN_PLAY_MS) {
365                self.player1.setTime(0);
366                return false;
367            }
368            const currentIndex = self.current_track_idx;
369            let newIndex = (currentIndex + 1) % links1.length;
370            if (newIndex === 0) { // 最後の曲の再生が終了した時
371                //self.player1.stop();
372                return false;
373            }
374            self.setCurrentTrack(newIndex);
375            const elem = $(links1[newIndex]);
376            self.showTrackInfo(elem);
377            $(links1).removeClass("now-playing");
378            $(links1).find('.track_play').removeClass('pause');
379            $(elem).addClass('now-playing');
380            $(elem).find('.track_play').addClass('pause');
381            self.changeAlbumInfo();
382        });
383    }
384}
385
386$(function () {
387    var audioPlayer = new AudioPlayer();
388});

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.