Search code examples
javascriptangulartypescriptspotify

Angular import script in index.html and use it in component


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",


Solution

  • 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-