Использование async-await с SDK Spotify Web Playback

Отказ от ответственности: я работаю в Spotify в команде разработчиков. Это личная запись в блоге об исследовании async-await с помощью нашего нового SDK.

Недавно моя команда в Spotify запустила SDK веб-воспроизведения для сторонних разработчиков. Теперь можно интегрировать воспроизведение контента Spotify внутри веб-сайтов и веб-приложений. Полное объявление читайте здесь.

С введением синтаксиса async-await в JavaScript ES6 я решил написать эту статью, чтобы продемонстрировать возможности этого нового синтаксиса с Web Playback SDK.

Привет, async-await!

Тем из вас, кто использовал Промисы в JavaScript, это легко объяснить. Если вы не использовали промисы или JavaScript, читать эту статью будет немного сложнее. Вы можете сначала поиграть с нашей демонстрацией Web Playback SDK на Glitch.

Простое объяснение, вот несколько псевдо-JavaScript для выполнения поиска и воспроизведения первой песни на Spotify:

function searchAndPlaySong (search_query) {
  return searchOnSpotify(search_query).then(results => {
    if (results.length > 0) {
      return playSong(results[0].spotify_uri);
    }
  });
}

И давайте посыпаем его солью async-await:

async function searchAndPlayFirstSong (search_query) {
  let results = await searchOnSpotify(search_query);
  if (results.length === 0) return;
  return playSong(results[0].spotify_uri);
}

Вот и все! Оба они выполняют одно и то же действие. Здесь ничего нового не происходит, это не новая функция, это не что иное, как синтаксический сахар для промисов. Вместо того, чтобы оборачивать наш код вложенными обещаниями, мы можем написать его, как указано выше, чтобы его было легче читать.

Когда мы пишем async, он возвращает обещание. Когда мы пишем await, мы ожидаем обещания. Он не запустит следующую строку кода, пока обещание не будет успешно возвращено. А для обработки ошибок вы можете просто использовать старый добрый JavaScript try {} catch (err) {}.

Использование async-await с SDK

Хотя в этом нет необходимости, я настоятельно рекомендую прочитать Руководство по быстрому запуску для Web Playback SDK, чтобы лучше понять, как настроить SDK внутри вашего собственного веб-приложения.

Загрузка SDK

Для тех, кто раньше не использовал SDK, это самый минимальный HTML-код, который вам понадобится для начала:

<html>
<body>
<script src="https://sdk.scdn.co/spotify-player.js"></script>
<script>
window.onSpotifyWebPlaybackSDKReady = () => {
  console.log("The Web Playback SDK is ready. We have access to Spotify.Player");
  console.log(window.Spotify.Player);
};
</script>
</body>
</html>

Когда страница загружается, Spotify автоматически загружает внешний <iframe>, содержащий сценарии, которые мы хотим скрыть от внешних разработчиков. После загрузки мы делаем доступным window.Spotify объект и немедленно вызываем window.onSpotifyWebPlaybackSDKReady обратный вызов, чтобы проинформировать разработчика.

Итак, имея это в виду, как это работает с async-await?

Первое, что нам нужно сделать, это создать асинхронный метод с именем waitForSpotifyWebPlaybackSDKToLoad, который должен проверять, был ли объект window.Spotify уже определен, или проверять, пока не будет запущен window.onSpotifyWebPlaybackSDKReady:

async function waitForSpotifyWebPlaybackSDKToLoad () {
  return new Promise(resolve => {
    if (window.Spotify) {
      resolve(window.Spotify);
    } else {
      window.onSpotifyWebPlaybackSDKReady = () => {
        resolve(window.Spotify);
      };
    }
  });
};

Тогда мы можем использовать его следующим образом:

(async () => {
  const { Player } = await waitForSpotifyWebPlaybackSDKToLoad();
  console.log("The Web Playback SDK has loaded.");
})();

Выглядит чище, с отличной абстракцией.

Создание экземпляра SDK

На этом этапе SDK должен загрузиться в вашем браузере, и вы сможете получить доступ к переменной Player внутри нашего асинхронного метода. Нам нужно передать имя игрока, начальный объем и токен доступа.

const sdk = new Player({
  name: "Web Playback SDK",
  volume: 1.0,
  getOAuthToken: callback => { callback("access token"); }
});

Вы можете спросить: почему мы здесь не используем async-await? Это потому, что это не асинхронно и не использует обещания. Когда мы создаем экземпляр Player, наша конфигурация сохраняется в памяти, и когда мы позже вызываем метод connect(), он выполняет запрос на создание проигрывателя внутри Spotify.

Подключение SDK к Spotify

Как уже упоминалось, нам нужно подключить наш локальный плеер к Spotify. Это асинхронный метод, как видно из примера, приведенного в Справочнике по SDK:

sdk.connect().then(connected => {
  if (connected) {
    // Connection was successful
  }
});

Наши друзья, ожидающие async-ожидания, могут помочь нам здесь:

let connected = await sdk.connect();
if (connected) {
  // Connection was successful
}

Получение состояния воспроизведения

После того, как ваш плеер подключится к Spotify с помощью connect(), он появится в Списке устройств пользователя в разделе SDK веб-воспроизведения (как мы определили ранее в new Player(...)) в любом официальном клиенте Spotify или через Веб-API в разделе GET /v1/me/player/devices.

Если пользователь не выбрал ваше устройство, оно вернет null. В противном случае SDK позволяет собирать текущее состояние локального воспроизведения. Давайте сделаем это и применим некоторую деструктуризацию:

let state = await sdk.getCurrentState();
if (state == null) {
  // Playback isn't on this device yet
} else {
  let {
    id,
    uri: track_uri,
    name: track_name,
    duration_ms,
    artists,
    album: {
      name: album_name,
      uri: album_uri,
      images: album_images
    }
  } = state.track_window.current_track;
  console.log(`You're listening to ${track_name} by ${artists[0].name}!`);
}

Это удобно предоставляет нам набор переменных: id, track_uri, track_name, duration_ms, artists, album_name, album_uri и album_images. Прекрасный!

Совет. Можно автоматически передавать воспроизведение пользователя на ваш плеер через веб-API с конечной точкой PUT /v1/me/player. Мы не будем рассказывать об этом для простоты.

Но что, если мы хотим подождать, пока пользователь выберет наше устройство? Мы можем определить метод с именем waitUntilUserHasSelectedPlayer, который проверяет getCurrentState(), пока он не вернет null:

async function waitUntilUserHasSelectedPlayer (sdk) {
  return new Promise(resolve => {
    let interval = setInterval(async () => {
      let state = await sdk.getCurrentState();
      if (state !== null) {
        resolve(state);
        clearInterval(interval);
      }
    });
  });
};

И тогда мы можем назвать это так:

let state = await waitUntilUserHasSelectedPlayer(sdk);

Запуск методов SDK

Подобно connect() и getCurrentState(), любой метод из Справочника SDK (например, пауза, возобновление, пропуск и т. Д.) Может использовать async-await, потому что все они возвращают обещания. Вот несколько примеров кода, чтобы показать вам:

Получить объем:

let volume = await sdk.getVolume();
console.log(`Got the volume: ${volume * 100}%`);

Установить громкость:

await sdk.setVolume(0.5);
console.log("Updated volume to 50%!");

Пауза:

await sdk.pause();
console.log("Paused!");

Резюме:

await sdk.resume();
console.log("Resumed!");

Пропустить:

await sdk.nextTrack();
console.log("Skipped track!");

Все методы доступны в Справочнике по SDK.

Работа с событиями SDK

Мы занимались загрузкой, созданием экземпляров, подключением, ожиданием, пока пользователь выберет наш проигрыватель, а также чтением и управлением нашим проигрывателем. А как насчет событий?

События - это действия, которые могут быть асинхронными с тем, что вы делаете. Вот несколько примеров:

  • У пользователя больше нет учетной записи Spotify Premium
  • Интернет-соединение потеряно
  • Обработка ошибок
  • Состояние воспроизведения изменилось

Эти действия асинхронны, но они не используют обещания. Они Слушатели событий. Из-за этого они не поддерживают async-await, но мы можем использовать их как обычно:

sdk.on("player_state_changed", state => {
  console.log("Playback State Changed", state);
});

Все поддерживаемые события доступны в Справочнике по SDK.

Окончательный код

Мы прошли через многое с помощью async-await. Подведем итоги:

  • Загрузка путем проверки того, что window.Spotify определен SDK
  • Создание нашего локального плеера с помощью new Player(...)
  • Подключение к нашему плееру с помощью connect()
  • Ожидание, чтобы пользователь выбрал наше устройство
  • Обработка событий с использованием существующего кода из SDK
  • Проверьте состояние локального воспроизведения

Как будет выглядеть код? Это может помочь:

window.onSpotifyWebPlaybackSDKReady = () => {};
async function waitForSpotifyWebPlaybackSDKToLoad () {
  return new Promise(resolve => {
    if (window.Spotify) {
      resolve(window.Spotify);
    } else {
      window.onSpotifyWebPlaybackSDKReady = () => {
        resolve(window.Spotify);
      };
    }
  });
};
async function waitUntilUserHasSelectedPlayer (sdk) {
  return new Promise(resolve => {
    let interval = setInterval(async () => {
      let state = await sdk.getCurrentState();
      if (state !== null) {
        resolve(state);
        clearInterval(interval);
      }
    });
  });
};
(async () => {
  const { Player } = await waitForSpotifyWebPlaybackSDKToLoad();
  const sdk = new Player({
    name: "Web Playback SDK",
    volume: 1.0,
    getOAuthToken: callback => { callback("access token"); }
  });
  sdk.on("player_state_changed", state => {
    // Update UI with playback state changes
  });
  let connected = await sdk.connect();
  if (connected) {
    let state = await waitUntilUserHasSelectedPlayer(sdk);
    await sdk.resume();
    await sdk.setVolume(0.5);
    let {
      id,
      uri: track_uri,
      name: track_name,
      duration_ms,
      artists,
      album: {
        name: album_name,
        uri: album_uri,
        images: album_images
      }
    } = state.track_window.current_track;
    console.log(`You're listening to ${track_name} by ${artists[0].name}!`);
  }
})();

В этом сила async-await! Код теперь легче выполнять, как список инструкций. Надеемся, что это упростит создание глубокой интеграции со Spotify.

Хотите демонстрацию async-await? Мы сделали его на Glitch. Проверьте это!

Резервная поддержка

Какая поддержка async-await? Неплохо. Он имеет около 73% поддержки в глобальных браузерах. Поскольку async-await - это всего лишь синтаксический сахар, мы можем просто использовать популярный транспилятор, такой как Babel.js, чтобы преобразовать наш JavaScript таким образом, чтобы изящно охватить 100% браузером.

Заключительные примечания

Мы работаем над оптимизацией возможностей для разработчиков, и я рекомендую попробовать Spotify for Developers. Если у вас есть отзывы или предложения, не стесняйтесь поделиться ими - мы будем рады услышать от вас!

Наконец, я не могу await увидеть, что вы создаете с помощью платформы Spotify!

Удачного взлома :)