В основном я использую аудиоплеер в Angular. Я взял аудио плеер из этого урока . Единственное, что я использую Jukebox, так что звук не может быть жестко запрограммирован, так как он будет меняться в зависимости от того, что находится в очереди пользователей. Я исследовал свою проблему и большинство решений, связанных с использованием темы для потокового аудио. У меня есть компонент проигрывателя, который обслуживается AudioService для воспроизведения и CloudService для извлечения файлов. Когда я пытаюсь наблюдать за очередью в PlayerComponent, я получаю неопределенную ошибку. Может ли кто-нибудь помочь мне с этим, или если у кого-то есть какие-то лучшие решения, могут ли они дать мне несколько советов.
player.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core';
import { StreamState } from '../_interfaces/stream-state';
import { AudioService } from '../_services/audio.service';
import { CloudService } from '../_services/cloud.service';
import { Song } from '../_interfaces/song';
import { Subject, PartialObserver, Observer } from 'rxjs';
const incoming = [{
url:
// tslint:disable-next-line: max-line-length
'https://ia801504.us.archive.org/3/items/EdSheeranPerfectOfficialMusicVideoListenVid.com/Ed_Sheeran_-_Perfect_Official_Music_Video%5BListenVid.com%5D.mp3',
songName: 'Perfect',
artist: ' Ed Sheeran'
},
{
url:
// tslint:disable-next-line: max-line-length
'https://ia801609.us.archive.org/16/items/nusratcollection_20170414_0953/Man%20Atkiya%20Beparwah%20De%20Naal%20Nusrat%20Fateh%20Ali%20Khan.mp3',
songName: 'Man Atkeya Beparwah',
artist: 'Nusrat Fateh Ali Khan'
},
{
url:
'https://ia801503.us.archive.org/15/items/TheBeatlesPennyLane_201805/The%20Beatles%20-%20Penny%20Lane.mp3',
songName: 'Penny Lane',
artist: 'The Beatles'
}];
@Component({
selector: 'app-player',
templateUrl: './player.component.html',
styleUrls: ['./player.component.scss']
})
export class PlayerComponent implements OnInit {
files: any[] = [];
state: StreamState;
currentFile: any = {};
constructor(
public audioService: AudioService,
public cloudService: CloudService
) {
cloudService.postSongs(incoming);
cloudService.getFiles().subscribe(files => {
this.files.push(files);
});
this.audioService.getState().subscribe(state => {
this.state = state;
});
}
ngOnInit() {
console.log(this.cloudService.queue.next());
}
playStream(url) {
this.audioService.playStream(url).subscribe(events => {
});
}
pause() {
this.audioService.pause();
}
play() {
this.audioService.play();
}
stop() {
this.audioService.stop();
}
next() {
const index = this.currentFile.index + 1;
const file = this.files[index];
this.openFile(file, index);
}
previous() {
const index = this.currentFile.index - 1;
const file = this.files[index];
this.openFile(file, index);
}
openFile(file, index) {
this.currentFile = { index, file };
this.audioService.stop();
this.playStream(file.url);
}
isFirstPlaying() {
return this.currentFile.index === 0;
}
isLastPlaying() {
return this.currentFile.index === this.files.length - 1 ;
}
onSliderChangeEnd(change) {
this.audioService.seekTo(change.value);
}
}
audio.service.ts
import { Injectable } from '@angular/core';
import { Observable, BehaviorSubject, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import * as moment from 'moment';
import { StreamState } from '../_interfaces/stream-state';
@Injectable({
providedIn: 'root'
})
export class AudioService {
private stop$ = new Subject();
private audioObj = new Audio();
audioEvents = [
'ended',
'error',
'play',
'playing',
'pause',
'timeupdate',
'canplay',
'loadedmetadata',
'loadstart'
];
private state: StreamState = {
playing: false,
readableCurrentTime: '',
readableDuration: '',
duration: undefined,
currentTime: undefined,
canplay: false,
error: false,
};
private stateChange: BehaviorSubject<StreamState> = new BehaviorSubject(
this.state
);
private updateStateEvents(event: Event): void {
switch (event.type) {
case 'canplay':
this.state.duration = this.audioObj.duration;
this.state.readableDuration = this.formatTime(this.state.duration);
this.state.canplay = true;
break;
case 'playing':
this.state.playing = true;
break;
case 'pause':
this.state.playing = false;
break;
case 'timeupdate':
this.state.currentTime = this.audioObj.currentTime;
this.state.readableCurrentTime = this.formatTime(
this.state.currentTime
);
break;
case 'error':
this.resetState();
this.state.error = true;
break;
}
this.stateChange.next(this.state);
}
private resetState() {
this.state = {
playing: false,
readableCurrentTime: '',
readableDuration: '',
duration: undefined,
currentTime: undefined,
canplay: false,
error: false
};
}
private streamObservable(url) {
return new Observable(observer => {
// Play audio
this.audioObj.src = url;
this.audioObj.load();
this.audioObj.play();
const handler = (event: Event) => {
this.updateStateEvents(event);
observer.next(event);
};
this.addEvents(this.audioObj, this.audioEvents, handler);
return () => {
// Stop Playing
this.audioObj.pause();
this.audioObj.currentTime = 0;
// remove event listeners
this.removeEvents(this.audioObj, this.audioEvents, handler);
// reset state
this.resetState();
};
});
}
private addEvents(obj, events, handler) {
events.forEach(event => {
obj.addEventListener(event, handler);
});
}
private removeEvents(obj, events, handler) {
events.forEach(event => {
obj.removeEventListener(event, handler);
});
}
playStream(url) {
return this.streamObservable(url).pipe(takeUntil(this.stop$));
}
play() {
this.audioObj.play();
}
pause() {
this.audioObj.pause();
}
stop() {
this.stop$.next();
}
seekTo(seconds) {
this.audioObj.currentTime = seconds;
}
formatTime(time: number, format: string = 'HH:mm:ss') {
const momentTime = time * 1000;
return moment.utc(momentTime).format(format);
}
getState(): Observable<StreamState> {
return this.stateChange.asObservable();
}
}
облако .service.ts
import { Injectable } from '@angular/core';
import { of, Subject, Observable} from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { Song } from '../_interfaces/song';
import { map, tap } from 'rxjs/operators';
import { environment } from '@environments/environment';
interface ResponseSongs {
results: Song[];
}
@Injectable({
providedIn: 'root'
})
export class CloudService {
queue: Subject<any>;
private url = `${environment.apiUrl}/assets/songs.json`;
getFiles() {
return this.queue.asObservable();
}
addToQueue(song) {
this.queue.next(song);
}
clearQueue() {
this.queue.next();
}
constructor(private http: HttpClient) {
this.queue = new Subject<Song[]>();
}
postSongs(incoming) {
return this.http.post<any>(`${environment.apiUrl}/songs/set`, incoming)
.pipe(map(songs => {
localStorage.setItem('songs', JSON.stringify(songs));
this.queue.next(songs);
return songs;
}));
}
getSongs(): Observable<any> {
return this.http.get(this.url)
.pipe(map(response => this.queue.next(response)));
}
}
backend.ts
import { Injectable } from '@angular/core';
import { HttpRequest, HttpResponse, HttpHandler, HttpEvent, HttpInterceptor, HTTP_INTERCEPTORS } from '@angular/common/http';
import { Observable, of, throwError } from 'rxjs';
import { delay, mergeMap, materialize, dematerialize } from 'rxjs/operators';
let users = JSON.parse(localStorage.getItem('users')) || [];
const songs = JSON.parse(localStorage.getItem('music')) || [];
@Injectable()
export class FakeBackendInterceptor implements HttpInterceptor {
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const { url, method, headers, body } = request;
// wrap in delayed observable to simulate server api call
return of(null)
.pipe(mergeMap(handleRoute))
.pipe(materialize())
.pipe(delay(500))
.pipe(dematerialize());
function handleRoute() {
switch (true) {
case url.endsWith('/users/authenticate') && method === 'POST':
return authenticate();
case url.endsWith('/users/register') && method === 'POST':
return register();
case url.endsWith('/users') && method === 'GET':
return getUsers();
case url.match(/\/users\/\d+$/) && method === 'DELETE':
return deleteUser();
case url.endsWith('/assets/songs.json') && method === 'GET':
return getSongs();
case url.endsWith('/songs/set') && method === 'POST':
return setSongs(body);
default:
// pass through any requests not handled above
return next.handle(request);
}
}
// route functions
function authenticate() {
const { username, password } = body;
const user = users.find(x => x.username === username && x.password === password);
if (!user) {return error('Username or password is incorrect'); }
return ok({
id: user.id,
username: user.username,
firstName: user.firstName,
lastName: user.lastName,
venueName: user.venueName,
token: 'fake-jwt-token'
});
}
function register() {
const user = body;
if (users.find(x => x.username === user.username)) {
return error('Username "' + user.username + '" is already taken');
}
user.id = users.length ? Math.max(...users.map(x => x.id)) + 1 : 1;
users.push(user);
localStorage.setItem('users', JSON.stringify(users));
return ok();
}
function getUsers() {
if (!isLoggedIn()) {return unauthorized(); }
return ok(users);
}
function deleteUser() {
if (!isLoggedIn()) { return unauthorized(); }
users = users.filter(x => x.id !== idFromUrl());
localStorage.setItem('users', JSON.stringify(users));
return ok();
}
function setSongs(incoming) {
const incomingSongs = incoming;
localStorage.setItem('songs', JSON.stringify(incomingSongs));
return ok();
}
function getSongs() {
return ok(songs);
}
// helper functions
// tslint:disable-next-line: no-shadowed-variable
function ok(body?) {
return of(new HttpResponse({ status: 200, body }));
}
function error(message) {
return throwError({ error: { message } });
}
function unauthorized() {
return throwError({ status: 401, error: { message: 'Unauthorised' } });
}
function isLoggedIn() {
return headers.get('Authorization') === 'Bearer fake-jwt-token';
}
function idFromUrl() {
const urlParts = url.split('/');
// tslint:disable-next-line: radix
return parseInt(urlParts[urlParts.length - 1]);
}
}
}
export const fakeBackendProvider = {
// use fake backend in place of Http service for backend-less development
provide: HTTP_INTERCEPTORS,
useClass: FakeBackendInterceptor,
multi: true
};
Сообщение об ошибке
PlayerComponent.html:10 ERROR TypeError: Cannot read property 'songName' of undefined
at Object.eval [as updateRenderer] (PlayerComponent.html:11)
at Object.debugUpdateRenderer [as updateRenderer] (core.js:45294)
at checkAndUpdateView (core.js:44277)
at callViewAction (core.js:44637)
at execEmbeddedViewsAction (core.js:44594)
at checkAndUpdateView (core.js:44272)
at callViewAction (core.js:44637)
at execComponentViewsAction (core.js:44565)
at checkAndUpdateView (core.js:44278)
at callViewAction (core.js:44637)