Home > Back-end >  Angular import script in index.html and use it in component
Angular import script in index.html and use it in component

Time:08-24

I'm trying to use the Spotify SDK in an Angular app. I can import the script from the CDN in index.html but can't figure out how I'm supposed to actually use it at the component level. I feel like I'm missing something basic here, like importing it somehow but I can't seem to get anything to work.

I know that's not much info to work off of, but really I just need to know how to use the imported script at a component level.

Error message on trying to use the SDK in the component level: Property 'onSpotifyWebPlaybackSDKReady' does not exist on type 'Window & typeof globalThis'

I also found this source code I thought I might try as an example to see how this all fits together. I don't understand how it all works, but in that project's package.json this line was included: "@types/spotify-web-playback-sdk": "0.1.9",

CodePudding user response:

Turns out @Konrad Linkowski's comment and simply declaring a variable for the required objects seems to fix this and allows full use of the library. There might be a more correct way of handling things, but this works without issue.

declare global {
  interface Window { onSpotifyWebPlaybackSDKReady: any; }
}
declare let Spotify: any;

window.onSpotifyWebPlaybackSDKReady = window.onSpotifyWebPlaybackSDKReady || {};

CodePudding user response:

You can put this code into a file with the suffix .d.ts anywhere in your src folder. I took the types from here: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/spotify-web-playback-sdk/index.d.ts and converted them into a module.

spotify.d.ts

export {};

declare global {
  interface Window {
    onSpotifyWebPlaybackSDKReady(): void;
    Spotify: typeof Spotify;
  }

  namespace Spotify {
    interface Entity {
      name: string;
      uri: string;
      url: string;
    }

    interface Album {
      name: string;
      uri: string;
      images: Image[];
    }

    interface Error {
      message: string;
    }

    type ErrorTypes =
      | 'account_error'
      | 'authentication_error'
      | 'initialization_error'
      | 'playback_error';

    interface Image {
      height?: number | null | undefined;
      url: string;
      size?: string | null | undefined;
      width?: number | null | undefined;
    }

    interface PlaybackContextTrack extends Entity {
      artists: Entity[];
      content_type: string;
      estimated_duration: number;
      group: Entity;
      images: Image[];
      uid: string;
    }

    interface PlaybackContextRestrictions {
      pause: string[];
      resume: string[];
      seek: string[];
      skip_next: string[];
      skip_prev: string[];
      toggle_repeat_context: string[];
      toggle_repeat_track: string[];
      toggle_shuffle: string[];
      peek_next: string[];
      peek_prev: string[];
    }

    interface PlaybackContextMetadata extends Entity {
      current_item: PlaybackContextTrack;
      next_items: PlaybackContextTrack[];
      previous_items: PlaybackContextTrack[];
      restrictions: PlaybackContextRestrictions;
      options: {
        repeat_mode: string;
        shuffled: boolean;
      };
    }

    interface PlaybackContext {
      metadata: PlaybackContextMetadata | null;
      uri: string | null;
    }

    interface PlaybackDisallows {
      pausing?: boolean;
      peeking_next?: boolean;
      peeking_prev?: boolean;
      resuming?: boolean;
      seeking?: boolean;
      skipping_next?: boolean;
      skipping_prev?: boolean;
      toggling_repeat_context?: boolean;
      toggling_repeat_track?: boolean;
      toggling_shuffle?: boolean;
    }

    interface PlaybackRestrictions {
      disallow_pausing_reasons?: string[];
      disallow_peeking_next_reasons?: string[];
      disallow_peeking_prev_reasons?: string[];
      disallow_resuming_reasons?: string[];
      disallow_seeking_reasons?: string[];
      disallow_skipping_next_reasons?: string[];
      disallow_skipping_prev_reasons?: string[];
      disallow_toggling_repeat_context_reasons?: string[];
      disallow_toggling_repeat_track_reasons?: string[];
      disallow_toggling_shuffle_reasons?: string[];
    }

    interface PlaybackState {
      context: PlaybackContext;
      disallows: PlaybackDisallows;
      duration: number;
      paused: boolean;
      position: number;
      loading: boolean;
      timestamp: number;
      /**
       * 0: NO_REPEAT
       * 1: ONCE_REPEAT
       * 2: FULL_REPEAT
       */
      repeat_mode: 0 | 1 | 2;
      shuffle: boolean;
      restrictions: PlaybackRestrictions;
      track_window: PlaybackTrackWindow;
      playback_id: string;
      playback_quality: string;
      playback_features: {
        hifi_status: string;
      };
    }

    interface PlaybackTrackWindow {
      current_track: Track;
      previous_tracks: Track[];
      next_tracks: Track[];
    }

    interface PlayerInit {
      name: string;
      getOAuthToken(cb: (token: string) => void): void;
      volume?: number | undefined;
    }

    type ErrorListener = (err: Error) => void;
    type PlaybackInstanceListener = (inst: WebPlaybackInstance) => void;
    type PlaybackStateListener = (s: PlaybackState) => void;
    type EmptyListener = () => void;

    type AddListenerFn = ((
      event: 'ready' | 'not_ready',
      cb: PlaybackInstanceListener
    ) => void) &
      ((event: 'autoplay_failed', cb: EmptyListener) => void) &
      ((event: 'player_state_changed', cb: PlaybackStateListener) => void) &
      ((event: ErrorTypes, cb: ErrorListener) => void);

    class Player {
      readonly _options: PlayerInit & { id: string };
      constructor(options: PlayerInit);

      connect(): Promise<boolean>;
      disconnect(): void;
      getCurrentState(): Promise<PlaybackState | null>;
      getVolume(): Promise<number>;
      nextTrack(): Promise<void>;

      addListener: AddListenerFn;
      on: AddListenerFn;

      removeListener(
        event: 'ready' | 'not_ready' | 'player_state_changed' | ErrorTypes,
        cb?: ErrorListener | PlaybackInstanceListener | PlaybackStateListener
      ): void;

      pause(): Promise<void>;
      previousTrack(): Promise<void>;
      resume(): Promise<void>;
      seek(pos_ms: number): Promise<void>;
      setName(name: string): Promise<void>;
      setVolume(volume: number): Promise<void>;
      togglePlay(): Promise<void>;

      activateElement(): Promise<void>;
    }

    interface Track {
      album: Album;
      artists: Entity[];
      duration_ms: number;
      id: string | null;
      is_playable: boolean;
      name: string;
      uid: string;
      uri: string;
      media_type: 'audio' | 'video';
      type: 'track' | 'episode' | 'ad';
      track_type: 'audio' | 'video';
      linked_from: {
        uri: string | null;
        id: string | null;
      };
    }

    interface WebPlaybackInstance {
      device_id: string;
    }
  }
}

Then to use it, you can create the global window function, and add the script programatically afterwards.

export class AppComponent {
  ngOnInit() {
    window.onSpotifyWebPlaybackSDKReady = () => {
      console.log('spotify script loaded');
      const player = new Spotify.Player({
        name: 'nameHere',
        getOAuthToken: () => {
          'functionHere';
        },
      });
      player.connect().then((success) => console.log('Connected:', success));
    };
    const script = document.createElement('script');
    script.src = 'https://sdk.scdn.co/spotify-player.js';
    document.body.appendChild(script);
  }
}

Alternatively you can install this package: https://www.npmjs.com/package/@types/spotify-web-playback-sdk

npm i @types/spotify-web-playback-sdk

But it isn't set up as a module so you will need to reference it manually like so

///  <reference types="@types/spotify-web-playback-sdk"/>

import { Component } from '@angular/core';

@Component({ ... })
export class AppComponent { ... }

That's a special typescript syntax for referencing types: https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html#-reference-types-

  • Related