Доступ к предметному контенту Angular для аудиоплеера - PullRequest
0 голосов
/ 26 марта 2020

В основном я использую аудиоплеер в 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)
...