Skip to content

Commit

Permalink
Proxy Crunchyroll CDN (remixz#13)
Browse files Browse the repository at this point in the history
* Proxy Crunchyroll CDN through Netlify

Allows us to bring back thumbnails on the scrubber and no more HTTPS
errors

* Add status code to cdn proxy on Netlify

* Reorder cdn proxy rule

* Fix proxy rule again

* Rewrite video poster URL

* Proxy rule needs the 200

* Move BIF parser to a web worker

* Move CRUNCHYROLL_CDN constant to lib/cdnRewrite

Not used by API, so we can avoid bundling that in the worker

* Update dependencies and supported browsers

I took a look at the analytics and found the majority of users are using
very modern browsers. So, I updated all of the browsers in babelrc for
the env preset (along with updating the preset itself for new data), and
noticed that all of the supported browsers support async functions
natively! Therefore, I don't need to include a polyfill for it anymore!
:tada:

* Don't cache /cdn/ URLs with the ServiceWorker

* Fix syntax

* Use ES5 syntax in webpack conf

* Update Safari and iOS Safari versions to 11

Unfortunately Safari 10.x has a browser bug for async arrow
functions nested inside another function
(https://bugs.webkit.org/show_bug.cgi?id=166879)

I could change the code not to use async arrow functions, but there are
a few places where I am actually taking advantage of using `this`, and
while I could just do some `const self = this` fun, I'd really rather
not optimize for one browser version's bug.

As well, looking at the analytics, in the past month only about 10 people
out of 2136 used a Safari 10.x version. I apologize to those people who
are losing support. Anyone who's on Mac and has a 10.x version can update
to Safari 11 though, even without installing High Sierra! For those on
iOS 10 and can't update because their device is too old... sorry!

* Make guest-message transparent when hidden

In Safari, the -50px margin at the top of the page is used in the
background of the UI, so the UI would have a yellow background when
scrolled down
  • Loading branch information
remixz authored Feb 4, 2018
1 parent 9227e86 commit 8e02c37
Show file tree
Hide file tree
Showing 16 changed files with 424 additions and 339 deletions.
16 changes: 5 additions & 11 deletions .babelrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,15 @@
["env", {
"modules": false,
"targets": {
"chrome": 56,
"firefox": 52,
"ios": 10,
"safari": 10,
"edge": 14
"chrome": 62,
"firefox": 56,
"ios": 11,
"safari": 11,
"edge": 15
},
"exclude": ["transform-regenerator", "transform-async-to-generator"],
"loose": true,
"useBuiltIns": true
}]
],
"plugins": [
["fast-async", {
"useRuntimeModule": true
}]
],
"comments": false
}
1 change: 1 addition & 0 deletions _redirects
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/cdn/* http://img1.ak.crunchyroll.com/:splat 200
/appcache/* /index.html 404
/* /index.html 200
12 changes: 11 additions & 1 deletion build/webpack.prod.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,17 @@ const webpackConfig = merge(baseWebpackConfig, {
events: true,
navigateFallbackURL: '/'
},
AppCache: false
AppCache: false,
cacheMaps: [{
match: function (url) {
if (url.pathname.indexOf('/cdn/') > -1) {
return
}

return new URL('/', location)
},
requestTypes: ['navigate']
}]
})
]
})
Expand Down
10 changes: 9 additions & 1 deletion config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,15 @@ module.exports = {
autoOpenBrowser: true,
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable: {},
proxyTable: {
'/cdn': {
target: 'http://img1.ak.crunchyroll.com',
changeOrigin: true,
pathRewrite: {
'^/cdn': ''
}
}
},
// CSS Sourcemaps off by default because relative paths are "buggy"
// with this option, according to the CSS-Loader README
// (https://github.com/webpack/css-loader#sourcemaps)
Expand Down
11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"axios": "^0.16.0",
"base64-js": "^1.2.0",
"clappr": "0.2.77",
"clappr-thumbnails-plugin": "^3.6.0",
"date-fns": "^1.27.2",
"debounce": "^1.0.2",
"firebase": "^4.6.2",
Expand All @@ -31,12 +32,12 @@
"vuex-router-sync": "^4.1.2"
},
"devDependencies": {
"autoprefixer": "^7.0.1",
"babel-core": "^6.22.1",
"autoprefixer": "^7.2.5",
"babel-core": "^6.26.0",
"babel-eslint": "^7.1.1",
"babel-loader": "^7.0.0",
"babel-polyfill": "^6.23.0",
"babel-preset-env": "^1.2.1",
"babel-preset-env": "^1.6.1",
"babel-register": "^6.22.0",
"chalk": "^1.1.3",
"connect-history-api-fallback": "^1.3.0",
Expand All @@ -53,7 +54,6 @@
"eventsource-polyfill": "^0.9.6",
"express": "^4.14.1",
"extract-text-webpack-plugin": "^3.0.0",
"fast-async": "^6.2.0",
"file-loader": "^0.11.1",
"friendly-errors-webpack-plugin": "^1.1.3",
"function-bind": "^1.1.0",
Expand All @@ -75,7 +75,8 @@
"webpack-bundle-analyzer": "^2.2.1",
"webpack-dev-middleware": "^1.10.0",
"webpack-hot-middleware": "^2.16.1",
"webpack-merge": "^4.0.0"
"webpack-merge": "^4.0.0",
"workerize-loader": "^1.0.1"
},
"engines": {
"node": ">= 4.0.0",
Expand Down
4 changes: 3 additions & 1 deletion src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,12 @@ export default {
.guest-message {
transform: translateY(-50px);
transition: transform 0.3s ease-in-out;
opacity: 0;
transition: all 0.3s ease-in-out;
}
.guest-message.active {
transform: translateY(0);
opacity: 1;
}
</style>
2 changes: 1 addition & 1 deletion src/components/MediaItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<router-link :to="`/series/${data.series_id}/${id}`" class="black" :class="{'pointer-events-none o-60': isRoomGuest}" v-if="data.available">
<div class="media-item dib v-top h-100 mr3 mb2 br2 bg-white" :class="[{'hide-child': !selected}, size]" @click="$emit('click')">
<div class="relative">
<img v-if="data.screenshot_image" :src="data.screenshot_image.full_url" class="w-100 image-size br2 br--top">
<img v-if="data.screenshot_image" :src="data.screenshot_image.full_url | cdnRewrite" class="w-100 image-size br2 br--top">
<div v-else class="w-100 image-size tc placeholder-image bg-light-gray">
<i class="fa fa-question-circle-o black-40 missing-icon" aria-hidden="true"></i>
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/components/QueueItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<router-link :to="`/series/${data.most_likely_media.series_id}/${data.most_likely_media.media_id}`" class="link black" v-if="data.most_likely_media.available">
<div class="w-100 mb2 pa3 cf hide-child br2 ba b--near-white box-shadow-umi">
<div class="fl w-20 relative">
<img :src="data.most_likely_media.screenshot_image.thumb_url" class="v-mid image-size">
<img :src="data.most_likely_media.screenshot_image.thumb_url | cdnRewrite" class="v-mid image-size">
<div class="bg-gray mt1 playhead">
<div class="bg-blue playhead" :style="playheadStyle"></div>
</div>
Expand All @@ -20,7 +20,7 @@
<router-link :to="`/series/${data.most_likely_media.series_id}`" class="link black" v-else>
<div class="bg-near-white w-100 mb2 pa3 cf bb bw2 b--light-gray hide-child">
<div class="fl w-20 relative">
<img :src="data.series.landscape_image.thumb_url" class="v-mid image-size">
<img :src="data.series.landscape_image.thumb_url | cdnRewrite" class="v-mid image-size">
<div class="child absolute bg-black-40 top-0 image-size">
<i class="fa fa-clock-o white tc play-icon" aria-hidden="true"></i>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Search.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
@mouseover.native="resultHover"
@click.native="resultPress"
>
<img class="v-mid" :src="series[id].landscape_image.small_url" :alt="series[id].name">
<img class="v-mid" :src="series[id].landscape_image.small_url | cdnRewrite" :alt="series[id].name">
<span class="truncate dib f6 v-mid">{{series[id].name}}</span>
</router-link>
</div>
Expand Down
48 changes: 26 additions & 22 deletions src/components/Video.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<div class="pv2">
<div id="player" class="bg-black w-100 absolute left-0 z-9999 player-top-offset" :class="{'shadow-2': lights}">
<div v-if="playerInit && showBlur && !lights" class="w-100 player-height absolute top-0 left-0 overflow-hidden o-80">
<img :src="poster" class="player-background" draggable="false">
<img :src="poster | cdnRewrite" class="player-background" draggable="false">
</div>
</div>
<div v-if="!playerInit" class="bg-black absolute w-100 left-0 player-height player-top-offset">
Expand All @@ -15,13 +15,17 @@
<script>
import Clappr from 'clappr'
import LevelSelector from 'lib/clappr-level-selector'
import Thumbnails from 'clappr-thumbnails-plugin'
import anime from 'animejs'
import uuid from 'uuid/v4'
import api, {LOCALE, VERSION} from 'lib/api'
import emoji from 'lib/emoji'
// import bif from 'lib/bif'
import bifWorker from 'workerize-loader!lib/bif'
import cdnRewrite from 'lib/cdnRewrite'
import Reactotron from './Reactotron'
const bif = bifWorker()
export default {
name: 'video',
props: ['data', 'poster', 'bif', 'id', 'seek', 'duration'],
Expand Down Expand Up @@ -65,10 +69,9 @@
width: '1024px',
height: '576px',
source: this.streamUrl,
poster: this.poster,
poster: cdnRewrite(this.poster),
disableVideoTagContextMenu: true,
// plugins: [LevelSelector, ClapprThumbnailsPlugin],
plugins: [LevelSelector],
plugins: [LevelSelector, Thumbnails],
levelSelectorConfig: {
title: 'Quality',
labels: {
Expand All @@ -79,11 +82,11 @@
0: '240p'
}
},
// scrubThumbnails: {
// backdropHeight: null,
// spotlightHeight: 84,
// thumbs: []
// },
scrubThumbnails: {
backdropHeight: null,
spotlightHeight: 84,
thumbs: []
},
events: {
onReady () {
if (self.container) {
Expand Down Expand Up @@ -135,7 +138,7 @@
}
if (this.bif) {
// this.loadBif()
this.loadBif()
}
},
watch: {
Expand All @@ -154,7 +157,7 @@
})
this.playback.on(Clappr.Events.PLAYBACK_PLAY_INTENT, this.roomHandlePlay)
}
// this.loadBif()
this.loadBif()
},
room (curr) {
if (curr === '') {
Expand Down Expand Up @@ -277,16 +280,17 @@
}
})
},
// async loadBif () {
// const thumbnailsPlugin = this.player.getPlugin('scrub-thumbnails')
// if (this.frames.length > 0) {
// thumbnailsPlugin.removeThumbnail(this.frames)
// }
// try {
// this.frames = await bif(this.bif)
// thumbnailsPlugin.addThumbnail(this.frames)
// } catch (err) {}
// },
async loadBif () {
const thumbnailsPlugin = this.player.getPlugin('scrub-thumbnails')
if (this.frames.length > 0) {
thumbnailsPlugin.removeThumbnail(this.frames)
}
this.frames = await bif.parse(this.bif)
if (this.frames.length > 0) {
thumbnailsPlugin.addThumbnail(this.frames)
}
},
wsJoinRoom () {
const {syncedTime, playing} = this.roomData
Expand Down
62 changes: 29 additions & 33 deletions src/lib/bif.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Based on https://github.com/chemoish/videojs-bif/blob/c6fdc0c2cfc9446927062995b7e8830ae45fff0d/src/parser.js
import axios from 'axios'
import { fromByteArray } from 'base64-js'
import { UMI_SERVER } from './api'
import cdnRewrite from './cdnRewrite'

const BIF_INDEX_OFFSET = 64
const FRAMEWISE_SEPARATION_OFFSET = 16
Expand All @@ -22,37 +21,34 @@ function validate (magicNumber) {
return MAGIC_NUMBER.every((byte, i) => magicNumber[i] === byte)
}

export default function bif (url) {
return new Promise(async (resolve, reject) => {
const {data: buf} = await axios.get(`${UMI_SERVER}/bif?bif=${url}`, {
responseType: 'arraybuffer'
export async function parse (url) {
const res = await fetch(cdnRewrite(url))
const buf = await res.arrayBuffer()

const magicNumber = new Uint8Array(buf).slice(0, 8)
if (!validate(magicNumber)) {
return []
}

const data = new DataView(buf)
const framewiseSeparation = data.getUint32(FRAMEWISE_SEPARATION_OFFSET, true) || 1000
const numberOfBIFImages = data.getUint32(NUMBER_OF_BIF_IMAGES_OFFSET, true)

const bifData = []
for (let i = 0, bifIndexEntryOffset = BIF_INDEX_OFFSET; i < numberOfBIFImages; i += 1, bifIndexEntryOffset += BIF_INDEX_ENTRY_LENGTH) {
const bifIndexEntryTimestampOffset = bifIndexEntryOffset
const bifIndexEntryAbsoluteOffset = bifIndexEntryOffset + 4
const nextBifIndexEntryAbsoluteOffset = bifIndexEntryAbsoluteOffset + BIF_INDEX_ENTRY_LENGTH

const offset = data.getUint32(bifIndexEntryAbsoluteOffset, true)
const nextOffset = data.getUint32(nextBifIndexEntryAbsoluteOffset, true)
const length = nextOffset - offset

bifData.push({
time: ((data.getUint32(bifIndexEntryTimestampOffset, true) * framewiseSeparation) / 1000) - 15,
url: `data:image/jpeg;base64,${fromByteArray(new Uint8Array(buf.slice(offset, offset + length)))}`
})
}

const magicNumber = new Uint8Array(buf).slice(0, 8)
if (!validate(magicNumber)) {
return reject(new Error('Invalid BIF file'))
}

const data = new DataView(buf)
const framewiseSeparation = data.getUint32(FRAMEWISE_SEPARATION_OFFSET, true) || 1000
const numberOfBIFImages = data.getUint32(NUMBER_OF_BIF_IMAGES_OFFSET, true)

const bifData = []
for (let i = 0, bifIndexEntryOffset = BIF_INDEX_OFFSET; i < numberOfBIFImages; i += 1, bifIndexEntryOffset += BIF_INDEX_ENTRY_LENGTH) {
const bifIndexEntryTimestampOffset = bifIndexEntryOffset
const bifIndexEntryAbsoluteOffset = bifIndexEntryOffset + 4
const nextBifIndexEntryAbsoluteOffset = bifIndexEntryAbsoluteOffset + BIF_INDEX_ENTRY_LENGTH

const offset = data.getUint32(bifIndexEntryAbsoluteOffset, true)
const nextOffset = data.getUint32(nextBifIndexEntryAbsoluteOffset, true)
const length = nextOffset - offset

bifData.push({
time: ((data.getUint32(bifIndexEntryTimestampOffset, true) * framewiseSeparation) / 1000) - 15,
url: `data:image/jpeg;base64,${fromByteArray(new Uint8Array(buf.slice(offset, offset + length)))}`
})
}

resolve(bifData)
})
return bifData
}
11 changes: 11 additions & 0 deletions src/lib/cdnRewrite.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const CRUNCHYROLL_CDN = 'http://img1.ak.crunchyroll.com/'

export default function cdnRewrite (url) {
if (typeof url !== 'string') return url

if (url.indexOf(CRUNCHYROLL_CDN) > -1) {
return url.replace(CRUNCHYROLL_CDN, '/cdn/')
} else {
return url
}
}
2 changes: 2 additions & 0 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import store from './store'
import { sync } from 'vuex-router-sync'
import Firebase from 'lib/firebase'
import VTooltip from 'v-tooltip'
import cdnRewrite from 'lib/cdnRewrite'

Vue.use(VTooltip)
Vue.use(Firebase)
sync(store, router)
Vue.filter('cdnRewrite', cdnRewrite)

/* eslint-disable no-new */
new Vue({
Expand Down
2 changes: 1 addition & 1 deletion src/pages/Media.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<div class="child absolute bg-black-40 tc next-episode-overlay">
<i class="fa fa-play white tc play-icon" aria-hidden="true"></i>
</div>
<img :src="nextEpisodeMedia.screenshot_image.thumb_url" class="v-mid bg-dark-gray next-episode-image">
<img :src="nextEpisodeMedia.screenshot_image.thumb_url | cdnRewrite" class="v-mid bg-dark-gray next-episode-image">
<div class="dib v-mid ml2">
Watch next episode: <br /> Episode {{nextEpisodeMedia.episode_number}} &mdash; {{nextEpisodeMedia.name}}
</div>
Expand Down
5 changes: 3 additions & 2 deletions src/pages/Series.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<div class="relative z-9999 info-container">
<div class="cf">
<div class="w-20 fl pt2">
<img :src="series.portrait_image.full_url" class="shadow-1 bg-light-gray portrait-image">
<img :src="series.portrait_image.full_url | cdnRewrite" class="shadow-1 bg-light-gray portrait-image">
</div>
<div class="w-80 fl pl3">
<h1 class="series-title white fw6">{{series.name || '&nbsp;'}}</h1>
Expand Down Expand Up @@ -53,6 +53,7 @@
import Collection from 'components/Collection'
import QueueButton from 'components/QueueButton'
import { UMI_SERVER } from 'lib/api'
import cdnRewrite from 'lib/cdnRewrite'
const openingOverrides = {
'269071': 'konosuba',
Expand Down Expand Up @@ -103,7 +104,7 @@
},
videoBannerStyle () {
return {
'background-image': `url(${this.series.landscape_image.full_url})`
'background-image': `url(${cdnRewrite(this.series.landscape_image.full_url)})`
}
}
},
Expand Down
Loading

0 comments on commit 8e02c37

Please sign in to comment.