-
-
Save mminer/43d86530a9479a3b5c725f7f118b64be to your computer and use it in GitHub Desktop.
using System; | |
using System.Text.RegularExpressions; | |
using UnityEngine; | |
/// <summary> | |
/// Holds the result of parsing the ytInitialPlayerResponse JSON from a YouTube page. | |
/// </summary> | |
/// <remarks> | |
/// This is an incomplete list of fields in ytInitialPlayerResponse. | |
/// The full object contains many more, but we only care about a few. | |
/// </remarks> | |
[Serializable] | |
public struct YouTubePlayerResponse | |
{ | |
[Serializable] | |
public struct PlayabilityStatus | |
{ | |
public string reason; | |
public string status; | |
} | |
[Serializable] | |
public struct StreamingData | |
{ | |
[Serializable] | |
public struct Format | |
{ | |
public int bitrate; | |
public string fps; | |
public string mimeType; | |
public string quality; | |
public string url; | |
/// <summary> | |
/// Whether this format is compatible with Unity's VideoPlayer. | |
/// </summary> | |
// TODO: this is probably needlessly restrictive | |
public bool IsCompatible => mimeType.Contains("video/mp4"); | |
} | |
public Format[] formats; | |
} | |
[Serializable] | |
public struct VideoDetails | |
{ | |
public string shortDescription; | |
public string title; | |
} | |
public PlayabilityStatus playabilityStatus; | |
public StreamingData streamingData; | |
public VideoDetails videoDetails; | |
// Example of unplayable video: https://www.youtube.com/watch?v=qm5q1o7ofnc | |
public bool IsPlayable => playabilityStatus.status != "ERROR"; | |
public static YouTubePlayerResponse FromJson(string json) | |
{ | |
return JsonUtility.FromJson<YouTubePlayerResponse>(json); | |
} | |
public static YouTubePlayerResponse? FromPageSource(string pageSource) | |
{ | |
// Extract the JSON from the JavaScript in the HTML. | |
var regex = new Regex(@"ytInitialPlayerResponse\s*=\s*(\{.+?\})\s*;", RegexOptions.Multiline); | |
var match = regex.Match(pageSource); | |
if (!match.Success) | |
{ | |
return null; | |
} | |
var json = match.Result("$1"); | |
return FromJson(json); | |
} | |
} |
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.Linq; | |
using UnityEngine; | |
using UnityEngine.Networking; | |
public enum YouTubeRequestResult | |
{ | |
InProgress, | |
Error, | |
Success, | |
} | |
/// <summary> | |
/// Provides a method to find URLs to a YouTube video's raw video files. | |
/// </summary> | |
public class YouTubeRequest | |
{ | |
public string Error { get; private set; } | |
public YouTubeRequestResult Result { get; private set; } | |
public YouTubePlayerResponse.StreamingData.Format BestQualityFormat => Formats | |
.OrderByDescending(format => format.bitrate) | |
.First(); | |
public List<YouTubePlayerResponse.StreamingData.Format> Formats => _playerResponse.streamingData.formats | |
.Where(format => format.IsCompatible) | |
.ToList(); | |
private YouTubePlayerResponse _playerResponse; | |
private readonly string _youTubeUrl; | |
/// <summary> | |
/// Creates a request to find a YouTube video files. | |
/// </summary> | |
/// <param name="youTubeUrl">YouTube video URL.</param> | |
public YouTubeRequest(string youTubeUrl) | |
{ | |
if (!YouTubeUtils.TryNormalizeYouTubeUrl(youTubeUrl, out _youTubeUrl)) | |
{ | |
throw new ArgumentException("Invalid YouTube URL.", nameof(youTubeUrl)); | |
} | |
} | |
/// <summary> | |
/// Downloads a list of video files for a YouTube video. | |
/// </summary> | |
public IEnumerator SendRequest() | |
{ | |
Result = YouTubeRequestResult.InProgress; | |
Debug.Log($"Fetching YouTube page source from {_youTubeUrl}"); | |
// Fetch the page source. That is, get the HTML that the browser downloads when you visit a YouTube page. | |
string pageSource; | |
using (var request = UnityWebRequest.Get(_youTubeUrl)) | |
{ | |
yield return request.SendWebRequest(); | |
if (request.result != UnityWebRequest.Result.Success) | |
{ | |
ReportError($"Error fetching YouTube page: {request.error}"); | |
yield break; | |
} | |
pageSource = request.downloadHandler.text; | |
} | |
// Extract video details from the page HTML. | |
var playerResponse = YouTubePlayerResponse.FromPageSource(pageSource); | |
if (!playerResponse.HasValue) | |
{ | |
ReportError("Unable to parse video details from YouTube page."); | |
yield break; | |
} | |
_playerResponse = playerResponse.Value; | |
Debug.Log($"Downloaded details for YouTube video: \"{playerResponse.Value.videoDetails.title}\""); | |
if (!playerResponse.Value.IsPlayable) | |
{ | |
ReportError($"YouTube video unplayable: {playerResponse.Value.playabilityStatus.reason}"); | |
yield break; | |
} | |
Result = YouTubeRequestResult.Success; | |
} | |
private void ReportError(string error) | |
{ | |
Error = error; | |
Result = YouTubeRequestResult.Error; | |
} | |
} |
using System; | |
using System.Web; | |
/// <summary> | |
/// Utility functions for interacting with YouTube. | |
/// </summary> | |
public static class YouTubeUtils | |
{ | |
/// <summary> | |
/// Pulls the video ID from a YouTube URL. | |
/// </summary> | |
/// <param name="youTubeUrl">YouTube URL to extract ID from.</param> | |
/// <returns>Canonical YouTube video ID (e.g. VZBYoN-iHkE).</returns> | |
public static string ExtractVideoId(string youTubeUrl) | |
{ | |
if (string.IsNullOrEmpty(youTubeUrl)) | |
{ | |
return null; | |
} | |
// YouTube URLs come in a few different formats. | |
// Normalize the URL such that the video ID appears in the query string. | |
youTubeUrl = youTubeUrl | |
.Trim() | |
.Replace("youtu.be/", "youtube.com/watch?v=") | |
.Replace("youtube.com/embed/", "youtube.com/watch?v=") | |
.Replace("/watch#", "/watch?"); | |
if (youTubeUrl.Contains("/v/")) | |
{ | |
var absolutePath = new Uri(youTubeUrl).AbsolutePath; | |
absolutePath = absolutePath.Replace("/v/", "/watch?v="); | |
youTubeUrl = $"https://youtube.com{absolutePath}"; | |
} | |
// The URL should now contain a query string of the format v={video-id}. | |
var queryString = new Uri(youTubeUrl).Query; | |
var query = HttpUtility.ParseQueryString(queryString); | |
return query.Get("v"); | |
} | |
/// <summary> | |
/// Normalizes a YouTube URL to the format https://youtube.com/watch?v={video-id}. | |
/// </summary> | |
/// <param name="youTubeUrl">YouTube URL to normalize.</param> | |
/// <param name="normalizedYouTubeUrl">Normalized YouTube URL.</param> | |
/// <returns>Whether normalization was successful and the URL is valid.</returns> | |
public static bool TryNormalizeYouTubeUrl(string youTubeUrl, out string normalizedYouTubeUrl) | |
{ | |
var videoId = ExtractVideoId(youTubeUrl); | |
if (string.IsNullOrEmpty(videoId)) | |
{ | |
normalizedYouTubeUrl = null; | |
return false; | |
} | |
normalizedYouTubeUrl = $"https://www.youtube.com/watch?v={videoId}&gl=US&hl=en&has_verified=1&bpctr=9999999999"; | |
return true; | |
} | |
} |
using System; | |
using System.Collections; | |
using UnityEngine; | |
using UnityEngine.Video; | |
public class YouTubeVideoPlayer : MonoBehaviour | |
{ | |
public VideoPlayer videoPlayer; | |
[InspectorName("YouTube URL")] | |
public string youTubeUrl; | |
private void Awake() | |
{ | |
if (videoPlayer == null) | |
{ | |
videoPlayer = GetComponent<VideoPlayer>(); | |
} | |
} | |
private void Start() | |
{ | |
StartCoroutine(SetVideoPlayerUrl()); | |
} | |
private IEnumerator SetVideoPlayerUrl() | |
{ | |
if (videoPlayer == null) | |
{ | |
Debug.LogError("No video player."); | |
yield break; | |
} | |
var request = new YouTubeRequest(youTubeUrl); | |
yield return request.SendRequest(); | |
if (request.Result == YouTubeRequestResult.Error) | |
{ | |
Debug.LogError($"Failed to fetch YouTube video details: {request.Error}"); | |
yield break; | |
} | |
Debug.Log("Fetched YouTube formats."); | |
try | |
{ | |
videoPlayer.url = request.BestQualityFormat.url; | |
} | |
catch (InvalidOperationException) | |
{ | |
Debug.LogError("Failed to find any compatible formats."); | |
} | |
} | |
} |
Hi,
I use Unity 2020.3.19f1 and i have an error
Can't play movie []
UnityEngine.StackTraceUtility:ExtractStackTrace ()
YoutubeVideoPlayer/d__4:MoveNext () (at Assets/YoutubeVideoPlayer.cs:47)
UnityEngine.SetupCoroutine:InvokeMoveNext (System.Collections.IEnumerator,intptr)
have you an idea ?
Thank's
@aphaena: It’s tough to say what the problem might be. It might be helpful to print the JSON string that YouTubePlayerResponse:FromPageSource
returns, or what the final URL that it sets on videoPlayer.url
is.
It's perfect when i active my gameobject with youtube video player after my app is started.
Thank's you for your share
Beautifull job :)
I don't know about you all but for me highest quality lags a lot even with a fiber connection on PC. But the code seems to work !
hello the video starts after 9-10 seconds how can i make that this time to 1-2 seconds
Thank you
Usage: attach to a game object that has a
VideoPlayer
component attached, or set aVideoPlayer
from another game object in the inspector field.This doesn't work in WebGL builds due to CORS.