Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Новая работа со станциями радио #589

Open
andronix1 opened this issue May 1, 2023 · 2 comments
Open

Новая работа со станциями радио #589

andronix1 opened this issue May 1, 2023 · 2 comments
Labels
feature Новая функция или улучшение старой

Comments

@andronix1
Copy link

Для работы станции надо:

  • создать сессию через /rotor/session/new и получить radio_session_id и sequence(настраивается с помощью флага)
  • оповестить о начале работы станции через /rotor/session/{radio_session_id}/feedback
  • оповестить об окончании трека через /rotor/session/{radio_session_id}/feedback
  • получить новые треки через /rotor/session/{radio_session_id}/tracks

Реализация класса Station с использованием новых методов API: https://pastebin.com/dJaHQmTp

@MarshalX
Copy link
Owner

MarshalX commented May 1, 2023

код с пастбина:

from datetime import datetime
from typing import List
import json

from yandex_music import ClientAsync, Sequence, Track

class DescriptionSeed:
    def __init__(self, value: str, tag: str, type: str, **kwargs):
        self.value = value
        self.tag = tag
        self.type = type

    def get_full_name(self, separator=':'):
        return f'{self.type}{separator}{self.tag}'
    
    def get_id_from(self):
        return f'radio-mobile-{self.get_full_name("-")}-default'

class StationSession:
    def __init__(self, radio_session_id: str, batch_id: str, pumpkin: bool, description_seed: DescriptionSeed, accepted_seeds: List[DescriptionSeed], **kwargs):
        self.radio_session_id = radio_session_id
        self.batch_id = batch_id
        self.pumpkin = pumpkin

        # Костыльно, но мне лень делать по другому :)
        self.description_seed = DescriptionSeed(**description_seed)
        self.accepted_seeds = [DescriptionSeed(**seed) for seed in accepted_seeds]

class PlaybackStatistics:
    def __init__(self, total_played_seconds: float, skipped: bool) -> None:
        self.total_played_seconds = total_played_seconds
        self.skipped = skipped
        
class Station:
    def __init__(self, client: ClientAsync, seeds: str | List[str]):
        '''
        Attributes:
            seed: str | List[str]
                В API указывается как seeds в массиве, но не смог найти условие, когда в длинна seeds не 1. Скорее всего это позволяет смешивать различные станции
                Пример seed: 'track:{track_id}', 'user:onyourwave'
        '''
        self.client = client
        self.seeds = [seeds] if isinstance(seeds, str) else seeds
        self.current_track_number = -1
        self.current_track_id = ''

    def __get_rotor_link(self, path) -> str:
        # Получение URL с сессией
        return f'{self.client.base_url}/rotor/session/{self.session_info.radio_session_id}{path}'

    async def __load_new_sequence(self):
        '''
        Загрузка новой последовательности треков. 
        В queue ОБЯЗАТЕЛЬНО долен быть первый трек из последовательности
        '''
        self.sequence = Sequence.de_list((await self.client.request.post(self.__get_rotor_link('/tracks'), json={
            "queue": [
                self.sequence[0].track.id
            ]
        }))['sequence'], self.client)
    
    def __get_current_timestamp(self) -> str:
        # Конвертирует время в формате UTC. Пример: "2023-05-01T10:35:14.604531+07:00"
        return datetime.now().astimezone().strftime('%Y-%m-%dT%H:%M:%S.%f%Z:00')
    
    async def __send_feedback(self, type: str, **kwargs):
        '''
        Метод для отправки связи
        Аргументы:
            **kwargs: dict - параметры запроса. указаны после типов запросов
            type: str
                radioStarted: отправлять перед запуском станции ОДИН РАЗ
                    from: str - id станции. Пример: user:onyourwave

                trackStarted: начало каждого трека
                    trackId: str - id трека

                skip: пропуск трека
                    trackId: str - id трека
                    totalPlayedSeconds: float - количество секунд

                trackFinished: отправлять при завершении трека
                    trackId: str - id трека
                    totalPlayedSeconds: float - количество секунд. В приложении ставится 0.1. Не знаю, обязательно ли это нужно
        '''
        await self.client.request.post(self.__get_rotor_link('/feedback'), json={
            'event': {
                'type': type,
                'timestamp': self.__get_current_timestamp(),
                **kwargs
            },
            'batchId': self.session_info.batch_id
        })

    async def new_session(self):
        session = await self.client.request.post(f'{self.client.base_url}/rotor/session/new', json = {
            'seeds': self.seeds,
            'includeTracksInResponse': True
        })
        self.session_info = StationSession(**session)
        self.sequence = Sequence.de_list(session['sequence'], self.client)
        await self.__send_feedback('radioStarted', **{
            'from': self.session_info.description_seed.get_id_from()
        })

    def set_playback_statistics(self, playback_statistics: PlaybackStatistics):
        self.playback_statistics = playback_statistics

    def __get_current_track(self) -> Track | None:
        return self.sequence[self.current_track_number].track if 0 <= self.current_track_number < len(self.sequence) else None

    async def next_track(self) -> Track:
        # Заканчиваем трек, если был
        if self.current_track_number != -1:
            await self.__send_feedback('skip' if self.playback_statistics.skipped else 'trackFinished', **{
                'trackId': self.current_track_id,
                'totalPlayedSeconds': self.playback_statistics.total_played_seconds if self.playback_statistics.skipped else 0.1
            })
        # Получаем следующий трек
        self.current_track_number += 1
        track = self.__get_current_track()
        if track is None:
            self.current_track_number = 0
            await self.__load_new_sequence()
            track = self.__get_current_track()
        self.current_track_id = track.id
        # Запуск трека
        await self.__send_feedback('trackStarted', **{
            'trackId': self.current_track_id,
        })
        return track

async def example(token: str):
    client = ClientAsync(token)
    await client.init()
    station = Station(client, "user:onyourwave")
    await station.new_session()
    while True:
        track = await station.next_track()
        print(', '.join([artist.name for artist in track.artists]), '-', track.title)
        station.set_playback_statistics(PlaybackStatistics(
            total_played_seconds=float(s) if (s := input('total_played_seconds: ')).replace('.', '', 1).isdigit() else 0.0,
            skipped=input('skipped? [Y/n]: ') in ['y', 'Y', '']
        ))

@MarshalX MarshalX added the feature Новая функция или улучшение старой label May 1, 2023
@MarshalX MarshalX changed the title Новая работа со станциями Новая работа со станциями радио May 1, 2023
@KirMozor
Copy link

KirMozor commented Jun 7, 2023

Для разработки своей API решил переделать данный код в Curl запросы (спасибо автору issue за помощь)
Вот все запросы которые делает данный код;

Создание сессии:

curl -X POST  \
         -H "Authorization: OAuth token"  \
         -H "Content-Type": "application/json"  \
          -d '{
                "seeds": ["user:onyourwave"],
                "includeTracksInResponse": true
           }'  \
           https://api.music.yandex.ru/rotor/session/new

От туда достаёте такие данные как: radioSessionId, batchId, sequence (список треков)

Фидбек:

  • Старт радио:
curl -X POST  \
                  -H "Content-Type: application/json"  \
                  -H "Authorization: OAuth token"  \
                  -d '{
                      "event": {
                                    "type": "radioStarted",
                                    "trackId": "Сюда свой trackId с которого начинается станция",
                                    "timestamp": "Время с которого начинается трек. Это для примера: 2023-06-06T13:33:00+00:00"
                       },
                       "batchId": "Сюда batchId из создании сессии"
                 }'  \
                 https://api.music.yandex.ru/rotor/session/Сюда radioSessionId/feedback
  • Запуск трека:
curl -X POST  \
                    -H "Content-Type: application/json"  \
                    -H "Authorization: OAuth token"  \
                    -d '{
                         "event": {
                                      "type": "trackStarted",
                                      "trackId": "Сюда свой trackId который вы начинаете прослушивать",
                                      "timestamp": "Время с которого начинается трек. Это для примера: 2023-06-06T13:33:00+00:00"
                         },
                         "batchId": "Сюда batchId из создании сессии"
                    }'  \
                       https://api.music.yandex.ru/rotor/session/Сюда radioSessionId/feedback
  • Пропуск трека:
curl -X POST  \ 
                     -H "Content-Type: application/json"  \
                     -H "Authorization: OAuth token"  \
                     -d '{
                          "event": {
                                       "type": "skip",
                                       "trackId": "Сюда свой trackId который вы пропускаете",
                                       "totalPlayedSeconds": "Сколько времени вы слушали, возьмём для примера 0.1 (именно писать 0.1)",
                                        "timestamp": "Время с которого начинается трек. Это для примера: 2023-06-06T13:33:00+00:00"
                            },
                            "batchId": "Сюда batchId из создании сессии"
                      }'  \
                       https://api.music.yandex.ru/rotor/session/Сюда radioSessionId/feedback
  • Конец трека:
curl -X POST  \
                      -H "Content-Type: application/json"  \
                      -H "Authorization: OAuth token"  \
                      -d '{
                          "event": {
                                      "type": "trackFinished",
                                      "trackId": "Сюда свой trackId который вы заканчиваете слушать",
                                      "totalPlayedSeconds": "Сколько времени вы слушали, возьмём для примера 0.1 (именно писать 0.1)",
                                      "timestamp": "Время с которого начинается трек. Это для примера: 2023-06-06T13:33:00+00:00"
                           },
                           "batchId": "Сюда batchId из создании сессии"
                        }'  \
                       https://api.music.yandex.ru/rotor/session/Сюда radioSessionId/feedback

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Новая функция или улучшение старой
Projects
None yet
Development

No branches or pull requests

3 participants