func (c *Client) GetPlaylistContext(ctx context.Context, url string) (*Playlist, error) {
	id, err := extractPlaylistID(url)
	if err != nil {
		return nil, fmt.Errorf("extractPlaylistID failed: %w", err)
	}
	requestURL := fmt.Sprintf(playlistFetchURL, id)
	resp, err := c.httpGet(ctx, requestURL)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	data, err := extractPlaylistJSON(resp.Body)
	if err != nil {
		return nil, err
	}
	p := &Playlist{ID: id}
	return p, json.Unmarshal(data, p)
}
GetPlaylistContext fetches playlist metadata, with a context, along with a list of Videos, and some basic information
for these videos. Playlist entries cannot be downloaded, as they lack all the required metadata, but
can be used to enumerate all IDs, Authors, Titles, etc.
package youtube
import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
)
type Client struct {
	Debug bool
	HTTPClient *http.Client
	decipherOpsCache DecipherOperationsCache
}
func (c *Client) GetVideo(url string) (*Video, error) {
	return c.GetVideoContext(context.Background(), url)
}
func (c *Client) GetVideoContext(ctx context.Context, url string) (*Video, error) {
	id, err := ExtractVideoID(url)
	if err != nil {
		return nil, fmt.Errorf("extractVideoID failed: %w", err)
	}
	return c.videoFromID(ctx, id)
}
func (c *Client) videoFromID(ctx context.Context, id string) (*Video, error) {
	body, err := c.videoDataByInnertube(ctx, id, Web)
	if err != nil {
		return nil, err
	}
	v := &Video{
		ID: id,
	}
	err = v.parseVideoInfo(body)
	if err == nil {
		return v, nil
	}
	if err == ErrNotPlayableInEmbed {
		html, err := c.httpGetBodyBytes(ctx, "https://www.youtube.com/watch?v="+id)
		if err != nil {
			return nil, err
		}
		return v, v.parseVideoPage(html)
	}
	if err == ErrLoginRequired {
		bodyEmbed, errEmbed := c.videoDataByInnertube(ctx, id, EmbeddedClient)
		if errEmbed == nil {
			errEmbed = v.parseVideoInfo(bodyEmbed)
		}
		if errEmbed == nil {
			return v, nil
		}
		if errEmbed == ErrVideoPrivate {
			return v, errEmbed
		}
		return v, fmt.Errorf("can't bypass age restriction: %w", errEmbed)
	}
	return v, err
}
type innertubeRequest struct {
	VideoID         string            `json:"videoId"`
	Context         inntertubeContext `json:"context"`
	PlaybackContext playbackContext   `json:"playbackContext"`
}
type playbackContext struct {
	ContentPlaybackContext contentPlaybackContext `json:"contentPlaybackContext"`
}
type contentPlaybackContext struct {
	SignatureTimestamp string `json:"signatureTimestamp"`
}
type inntertubeContext struct {
	Client innertubeClient `json:"client"`
}
type innertubeClient struct {
	HL            string `json:"hl"`
	GL            string `json:"gl"`
	ClientName    string `json:"clientName"`
	ClientVersion string `json:"clientVersion"`
}
type ClientType string
const (
	Web            ClientType = "WEB"
	EmbeddedClient ClientType = "WEB_EMBEDDED_PLAYER"
)
func (c *Client) videoDataByInnertube(ctx context.Context, id string, clientType ClientType) ([]byte, error) {
	sts, err := c.getSignatureTimestamp(ctx, id)
	if err != nil {
		return nil, err
	}
	data, keyToken := prepareInnertubeData(id, sts, clientType)
	reqData, err := json.Marshal(data)
	if err != nil {
		return nil, err
	}
	u := fmt.Sprintf("https://www.youtube.com/youtubei/v1/player?key=%s", keyToken)
	req, err := http.NewRequest(http.MethodPost, u, bytes.NewReader(reqData))
	if err != nil {
		return nil, err
	}
	resp, err := c.httpDo(req)
	if err != nil {
		return nil, err
	}
	defer func() {
		_ = resp.Body.Close()
	}()
	return io.ReadAll(resp.Body)
}
var innertubeClientInfo = map[ClientType]map[string]string{
	Web: {
		"version": "2.20210617.01.00",
		"key":     "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8",
	},
	EmbeddedClient: {
		"version": "1.19700101",
		"key": "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8",
	},
}
func prepareInnertubeData(videoID string, sts string, clientType ClientType) (innertubeRequest, string) {
	cInfo, ok := innertubeClientInfo[clientType]
	if !ok {
		clientType = Web
		cInfo = innertubeClientInfo[clientType]
	}
	return innertubeRequest{
		VideoID: videoID,
		Context: inntertubeContext{
			Client: innertubeClient{
				HL:            "en",
				GL:            "US",
				ClientName:    string(clientType),
				ClientVersion: cInfo["version"],
			},
		},
		PlaybackContext: playbackContext{
			ContentPlaybackContext: contentPlaybackContext{
				SignatureTimestamp: sts,
			},
		},
	}, cInfo["key"]
}
func (c *Client) GetPlaylist(url string) (*Playlist, error) {
	return c.GetPlaylistContext(context.Background(), url)
}