Created
January 28, 2021 00:58
-
-
Save mminer/43d86530a9479a3b5c725f7f118b64be to your computer and use it in GitHub Desktop.
YouTube video player for Unity.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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."); | |
} | |
} | |
} |
@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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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