Switching between Youku and YouTube based on whether you're in China
Asked Answered
M

7

16

I'm hosting a conference website where I want to embed a Youku video if you're in China and a YouTube video otherwise. The conference website is being served through a CDN inside the Great Firewall. I was given the code below to switch between internal and external versions of Youku.

Unfortunately, ipinfo.io does not seem to be accessible inside the Great Firewall, so the code times out after 10 seconds.

I've considered rewriting the page to use a Youku video by default, writing a small, non-blocking JavaScript function that tries to reach YouTube. If it can, replace the Youku with YouTube. If it can't, exit harmlessly. That way, the reachability of YouTube becomes the key test, not whether you're in China.

Alternatively, I've thought of hosting the video on my site, so that it will be replicated via the CDN inside the Great Firewall. However, that would mean that the video would always be downloaded at full resolution, even if you're on a slow connection.

Any better suggestions on how to switch between Youku and Youtube, or more generally have a video playable both inside and outside China?

jQuery.get("https://ipinfo.io", function(response) {
    var country = response.country;

    if(country == 'CN') {
        youku.attr('src',chinaVideo)
    } else {
        youku.attr('src',generalVideo)
    }
}, "jsonp");
Mitchel answered 21/7, 2018 at 2:35 Comment(5)
By keeping a common code base for all, you can make a separate cartridge for china, and write the youku code for your video in that cartridge, whenever user hits the URL, if the URL contains the id for the Chinese locale, then that code can override the default code.Whitecap
You should check for it only once and keep the result in a cookie, so a visitor only needs to wait for the first time he visits the site. If this video is not on the front page, by the time he makes it to the conference video, you can have it checked trying to access youtube and set the cookie.Avigation
Thanks, but video is only on the front page.Mitchel
Don't you have CDN settings for that? Can you just push the youku-first version of scripts to CDN nodes near China users?Zaidazailer
@dankohn, please think about that your bounty will ending tomorrow & you cannot award a bounty to your own answer (in this case no bounty will be awarded to anyone).Spool
M
6

Here is the JavaScript we are going with:

$(document).ready(function (){
    var country = '',
    youku = $('#youku');

    $.ajax({
        url: "https://ipinfo.io",
        dataType: "jsonp",
        success: function(response){
            var country = response.country;

            if(country != 'CN') {
                youku.attr('src','https://www.youtube.com/embed/K3cEE5h7c1s')
            }
         },
         error: function(){
            console.log('sorry...')
         },
         timeout: 5000
    });     
});

We are including the Youku link in the HTML and switching to YouTube if country is not China. This still works if connecting to ipinfo.io times out, which sometimes happens in China.

Edit: revised to add a 5 second timeout.

Edit2: We implemented this as an open source Wordpress plugin, in case others are interested. https://github.com/cncf/china-video-block

Mitchel answered 24/7, 2018 at 4:55 Comment(4)
@GrantWinney, it looks like an answer until you read the question.Avigation
@GrantWinney the way OP is currently doing it is not an answer, it is part of the question. And I don't think OP was asking how can i select the jQuery element, please don't judge a question just reading the title.Avigation
It is the answer we're planning to go with. I remain open to hearing a better one.Mitchel
@dankohn What are the specific characteristics you're looking for in a better answer?Bedel
A
5

It is better to specifically check if YouTube is blocked in user's network rather than filtering out country based on IP which is never 100% reliable. This way it will even work in any school/office environment where YouTube could be blocked by network administrator for any reason.

This is the best answer I could find in this regard which tries to load the favicon of YouTube via JavaScript. If it succeeds to download that tiny image, YouTube is accessible. The image is only 0.2 kilobytes of download and it is most likely already cached by any user who ever visited YouTube.

That means the result is almost instantaneous for users with YouTube access and will take few seconds for users with blocked firewalls. The code is simple:

jQuery(function(){
    var youku = jQuery('#youku');
    var image = new Image();
    image.onload = function(){
        youku.attr('src','https://www.youtube.com/embed/K3cEE5h7c1s')
    };
    image.onerror = function(){
        youku.attr('src','https://www.youku.com/embed/K3cEE5h7c1s')
    };
    image.src = "https://youtube.com/favicon.ico";
});

This one is a better solution than stated above as it does not wait for document.ready event of jQuery, the image starts to load instantly.

(function(){
    var image = new Image();
    image.onload = function(){
        jQuery(function(){
            jQuery('#youku').attr('src','https://www.youtube.com/embed/K3cEE5h7c1s')
        });
    };
    image.onerror = function(){
        jQuery(function(){
            jQuery('#youku').attr('src','https://www.youku.com/embed/K3cEE5h7c1s')
        });
    };
    image.src = "https://youtube.com/favicon.ico";
})();

Update based on comments:

In case there is possibility of the image being cached previously on user's computer, adding the current timestamp parameter to the image URL will ensure the image is loaded despite previously cached.

    image.src = "https://youtube.com/favicon.ico?_=" + Date.now();
Avigation answered 25/7, 2018 at 21:2 Comment(2)
This is very clever. There's a challenge that many technical users in China occasionally use a VPN, and so may already have the YouTube URL cached. But, Youku is still almost certainly a better choice for them.Mitchel
In that case use a cache burster url for favicon so that it always gets downloaded eg. favicon.ico?_=<random-number>Avigation
B
0

Your current solution is not ideal as it depends on the GET request timing out, which is going to slow things down. Since the issue largely revolves around ipinfo.io being blocked in China, it's likely that an effective solution would involve an IP identification service based within China.

Try using http://www.ip138.com/. Google Translate works well enough to read it in English. There doesn't seem to be a formal JSON API, but you can hit http://www.ip138.com/ips138.asp?ip=<IP_ADDRESS_HERE> and get the location info.

To do so, navigate the DOM with jQuery (since you're already using it) to the line with 本站数据:, which will precede the name of the country. I think the selector for that as the page is currently constructed is simply $( "li" ).first(). Extract the string after the colon and the POST it to the Google Translate API to get the country name in English and voila! You have a faster and more reliable (if considerably more complicated) way to see if a visitor is in China or not.

EDIT: It is not entirely clear if you possess the user's IP address beforehand. If not, you can use a similar method with https://geoiptool.com/zh/, which is also based within China and thus not blocked. I'm happy to flesh this part out as needed.

Bedel answered 25/7, 2018 at 18:26 Comment(2)
138.com is a nice option except that they don't support https. Do you know of a site that does?Mitchel
What about https://www.ip.cn/index.php?ip=<IP_ADDRESS_HERE>, using the same process?Bedel
Z
0

Any better suggestions on how to switch between Youku and Youtube, or more generally have a video playable both inside and outside China?

I'd suggest taking a look at Alibaba Cloud. They have a bunch of services, but AFAIK there is no free plan.
Since I'm not familiar with other cloud providers for China, let me describe a solution for Alibaba Cloud.

I see two possible options for you:

  1. use their CDN Service and create two JS bundles - one for China and second for the rest of the world. The first one will embed video from Youku and second - from Youtube. After uploading them to CDN, you can force-push them to local nodes in China/world so there is always a quick and up-to-date response for users;
  2. use their Media Transcoding Service for video processing + Object Storage Service for video hosting + CDN Service for distribution. This way you can create multiple videos of different quality and distribute them near your users. JS will embed the video directly from CDN. For one video this might be an overkill.
Zaidazailer answered 25/7, 2018 at 21:11 Comment(3)
I'm already using Alibaba cloud's CDN to host the content inside the Great Firewall. I'm hesitant to host the video locally because I'd like it to dynamically play at the right bandwdith.Mitchel
oh, then you can distribute youku version of js bundle in China region, right?Zaidazailer
and yeah, if you host your videos on your own (say, OSS+CDN), and even if you transcode them into multiple videos of different quality, you still have to bother about detecting user's internet quality (-_-)Zaidazailer
T
0

What about using the client's time zone? While not perfect, it would allow you to avoid relying on timeouts or any geoip service requests altogether. If possible, I would avoid an approach that might result in many, repeated calls to the same blocked resource from the same address block (e.g. from conference attendees). When I last lived in China, I remember triggering TCP resets from the Great Firewall which would last a good 30 seconds or more.

Intl.DateTimeFormat().resolvedOptions().timeZone is reasonably well-supported in modern browsers. In China, it should return the IANA time zone for China: "Asia/Shanghai":

console.log(Intl.DateTimeFormat().resolvedOptions().timeZone)

There are also libraries (jsTimezoneDetect and moment-timezone) that combine the above along with the getTimezoneOffset function of Date across multiple points in time, to make an educated guess of time zone, which may yield better results with older browsers:

console.log(jstz.determine().name());
console.log(moment.tz.guess());
<script src="//cdnjs.cloudflare.com/ajax/libs/jstimezonedetect/1.0.6/jstz.min.js"></script>

<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.5/moment-timezone-with-data-2010-2020.min.js"></script>

For my part, I'd probably try detecting the time zone as "Asia/Shanghai" and fallback to a time zone offset of "UTC+8" (time zone offset alone will catch more than mainland China but I would imagine that's an acceptable overreach for a fallback strategy), e.g.:

var ianaTz = Intl.DateTimeFormat().resolvedOptions().timeZone;
var offset = new Date().getTimezoneOffset();

if (ianaTz === "Asia/Shanghai" || (ianaTz == null && offset/60 == -8)) {
  console.log("We'll assume China");
} else {
  console.log("not China");
}
Thorwald answered 25/7, 2018 at 21:42 Comment(0)
B
0

You could use ffmpeg to convert the video to a HLS version, make the HLS playlist(.m3u8) by hand based on RFC 8216. ffmpeg does not create the master playlist for you. HLS helps adapting the resolution to the internet speed. If you combine this with the free hls.js javascript mediaplayer, you can host it on your own site in order to pass through the great firewall of china, and support adaptive resolution.

Here's an example HLS master playlist so you don't necessarily need to read RFC 8216, the file would be called Video.m3u8:

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=1920x1080
Video_1080p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=9000000,RESOLUTION=2560x1440
Video_1440p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=16000000,RESOLUTION=3840x2160
Video_4k.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2800000,RESOLUTION=1280x720
Video_720p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1400000,RESOLUTION=842x480
Video_480p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=800000,RESOLUTION=640x360
Video_360p.m3u8

The bandwidth is specified in bits per second, the quality order does not matter as the mediaplayer chooses which resolution to play.

The command you can use for ffmpeg in the command prompt is:

ffmpeg -hwaccel dxva2 -hide_banner -y -i "C:\input\Video.mp4" -vf scale=1920:1080 -c:a aac -ar 48000 -c:v h264 -profile:v main -crf 20 -r 30 -sc_threshold 0 -g 48 -keyint_min 48 -hls_time 2 -hls_playlist_type vod -b:v 5000k -maxrate 5350k -bufsize 7500k -b:a 192k -hls_segment_filename "C:\output\Video_1080p_%03d.ts" "C:\output\Video_1080p.m3u8" -vf scale=2560:1440 -c:a aac -ar 48000 -c:v h264 -profile:v main -crf 20 -r 30 -sc_threshold 0 -g 48 -keyint_min 48 -hls_time 2 -hls_playlist_type vod -b:v 9000k -maxrate 10350k -bufsize 14500k -b:a 192k -hls_segment_filename "C:\output\Video_1440p_%03d.ts" "C:\output\Video_1440p.m3u8" -vf scale=3840:2160 -c:a aac -ar 48000 -c:v h264 -profile:v main -crf 20 -r 30 -sc_threshold 0 -g 48 -keyint_min 48 -hls_time 2 -hls_playlist_type vod -b:v 16000k -maxrate 20350k -bufsize 26000k -b:a 192k -hls_segment_filename "C:\output\Video_4k_%03d.ts" "C:\output\Video_4k.m3u8" -vf scale=1280:720 -c:a aac -ar 48000 -c:v h264 -profile:v main -crf 20 -r 30 -sc_threshold 0 -g 48 -keyint_min 48 -hls_time 2 -hls_playlist_type vod -b:v 2800k -maxrate 2996k -bufsize 4200k -b:a 128k -hls_segment_filename "C:\output\Video_720p_%03d.ts" "C:\output\Video_720p.m3u8" -vf scale=842:480 -c:a aac -ar 48000 -c:v h264 -profile:v main -crf 20 -r 30 -sc_threshold 0 -g 48 -keyint_min 48 -hls_time 2 -hls_playlist_type vod -b:v 1400k -maxrate 1498k -bufsize 2100k -b:a 128k -hls_segment_filename "C:\output\Video_480p_%03d.ts" "C:\output\Video_480p.m3u8" -vf scale=640:360 -c:a aac -ar 48000 -c:v h264 -profile:v main -crf 20 -r 30 -sc_threshold 0 -g 48 -keyint_min 48 -hls_time 2 -hls_playlist_type vod -b:v 800k -maxrate 865k -bufsize 1200k -b:a 96k -hls_segment_filename "C:\output\Video_360p_%03d.ts" "C:\output\Video_360p.m3u8"

The only thing you would need to to is replace C:\input\Video.mp4 with your video and replace all C:\output\Video with an output name for the video. 360p stands for the resolution specified in the output video file name, it does not influence the actual resolution, %03d is a command which is parsed to give the video fragment a number such as 001 until 999. The underscore is used for readability.

Explaination of the comments: -hwaccel dxva2 means that ffmpeg will use hardware acceleration using directx, this is available from windows vista and up. -hls_time 2 sets the fragment time to 2 seconds per fragment. aac and h264 are the codecs to use in the hls video fragments.

This command for ffmpeg generates 6 quality levels but does not create the master playlist. The master playlist is a playlist with playlist per bandwidth(see above), depending on the internet speed the hls player chooses which playlist of video fragments to play.

Barriebarrientos answered 26/7, 2018 at 9:50 Comment(3)
This is interesting. But HLS seems not to play reliably on iOS. #43287726Mitchel
That's odd, HLS was developed by apple, could it be an issue because of an unsupported codec? Or perhaps the mime-types used by the webserver?Barriebarrientos
The links referenced in that post talk about CORS being the issue. Hosting the video on the same domain as the video player works, videos play fine on our Mac virtual machine and on an IpadBarriebarrientos
S
0

GETTING LOCALE INFORMATION FROM BROWSERS USING JAVASCRIPT

To make the detection process faster you could first try to extract locale information from the browser package installation language:

navigator.language
navigator.languages

and then combine that or, as a last resource, fallback to geographic location or other means to detect user language preferences from the Accept-Language HTTP header.

The following is an example of external service to grab the browser headers:

https://ajaxhttpheaders1.appspot.com/?callback=getHeaders

Another path that might be combined with the above solution is to grab some string from the user environment or the browsing context (window.location, document.cookie, document.localStorage, Date() or Date().toLocaleString() et similar) and scan these string for Chinese Unicode codepoint using Regular Expression Unicode Ranges.

I believe this will get you a very good approximation without making extra requests. As an example in my Firefox browser a call too Date().toLocaleString() produces:

"Mon Jul 30 2018 01:35:38 GMT+0200 (Ora legale dell’Europa centrale)"

which can be easily parsed be as "Italian" locale.

Somewhere you will find a Unicode point of a Chinese ideogram/symbol.

Swan answered 29/7, 2018 at 23:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.