diff --git a/renderer/index.js b/renderer/index.js
index dcd48955..42094769 100644
--- a/renderer/index.js
+++ b/renderer/index.js
@@ -31,9 +31,13 @@ global.WEBTORRENT_ANNOUNCE = createTorrent.announceList
})
var state = global.state = {
- server: null, /* local WebTorrent-to-HTTP server */
- view: {
+ /* Temporary state disappears once the program exits.
+ * It can contain complex objects like open connections, etc.
+ */
+ temp: {
url: '/',
+ client: null, /* the WebTorrent client */
+ server: null, /* local WebTorrent-to-HTTP server */
dock: {
badge: 0,
progress: 0
@@ -42,19 +46,41 @@ var state = global.state = {
airplay: null, /* airplay client. finds and manages AppleTVs */
chromecast: null /* chromecast client. finds and manages Chromecasts */
},
- client: null, /* the WebTorrent client */
torrentPlaying: null, /* the torrent we're streaming. see client.torrents */
// history: [], /* track how we got to the current view. enables Back button */
// historyIndex: 0,
isFocused: true,
isFullScreen: false,
mainWindowBounds: null, /* x y width height */
- title: 'WebTorrent' /* current window title */
+ title: 'WebTorrent', /* current window title */
+ video: {
+ isPaused: false,
+ currentTime: 0, /* seconds */
+ duration: 1 /* seconds */
+ }
},
- video: {
- isPaused: false,
- currentTime: 0, /* seconds */
- duration: 1 /* seconds */
+ /* Saved state is read from and written to ~/.webtorrent/state.json
+ * It should be simple and minimal and must be JSONifiable
+ */
+ saved: {
+ torrents: [
+ {
+ name: 'Elephants Dream',
+ torrentFile: 'resources/ElephantsDream_archive.torrent'
+ },
+ {
+ name: 'Big Buck Bunny',
+ torrentFile: 'resources/BigBuckBunny_archive.torrent'
+ },
+ {
+ name: 'Sintel',
+ torrentFile: 'resources/Sintel_archive.torrent'
+ },
+ {
+ name: 'Tears of Steel',
+ torrentFile: 'resources/TearsOfSteel_archive.torrent'
+ }
+ ]
}
}
@@ -64,7 +90,7 @@ function init () {
client = global.client = new WebTorrent()
client.on('warning', onWarning)
client.on('error', onError)
- state.view.client = client
+ state.temp.client = client
vdomLoop = mainLoop(state, render, {
create: createElement,
@@ -78,12 +104,12 @@ function init () {
dragDrop('body', onFiles)
chromecasts.on('update', function (player) {
- state.view.devices.chromecast = player
+ state.temp.devices.chromecast = player
update()
})
airplay.createBrowser().on('deviceOn', function (player) {
- state.view.devices.airplay = player
+ state.temp.devices.airplay = player
}).start()
document.addEventListener('paste', function () {
@@ -92,7 +118,7 @@ function init () {
document.addEventListener('keydown', function (e) {
if (e.which === 27) { /* ESC means either exit fullscreen or go back */
- if (state.view.isFullScreen) {
+ if (state.temp.isFullScreen) {
dispatch('toggleFullScreen')
} else {
dispatch('back')
@@ -101,13 +127,13 @@ function init () {
})
window.addEventListener('focus', function () {
- state.view.isFocused = true
- if (state.view.dock.badge > 0) electron.ipcRenderer.send('setBadge', '')
- state.view.dock.badge = 0
+ state.temp.isFocused = true
+ if (state.temp.dock.badge > 0) electron.ipcRenderer.send('setBadge', '')
+ state.temp.dock.badge = 0
})
window.addEventListener('blur', function () {
- state.view.isFocused = false
+ state.temp.isFocused = false
})
}
init()
@@ -126,16 +152,16 @@ setInterval(function () {
}, 1000)
function updateDockIcon () {
- var progress = state.view.client.progress
- var activeTorrentsExist = state.view.client.torrents.some(function (torrent) {
+ var progress = state.temp.client.progress
+ var activeTorrentsExist = state.temp.client.torrents.some(function (torrent) {
return torrent.progress !== 1
})
// Hide progress bar when client has no torrents, or progress is 100%
if (!activeTorrentsExist || progress === 1) {
progress = -1
}
- if (progress !== state.view.dock.progress) {
- state.view.dock.progress = progress
+ if (progress !== state.temp.dock.progress) {
+ state.temp.dock.progress = progress
electron.ipcRenderer.send('setProgress', progress)
}
}
@@ -164,19 +190,19 @@ function dispatch (action, ...args) {
setDimensions(args[0] /* dimensions */)
}
if (action === 'back') {
- if (state.view.url === '/player') {
+ if (state.temp.url === '/player') {
restoreBounds()
closeServer()
}
- state.view.url = '/'
+ state.temp.url = '/'
update()
}
if (action === 'playPause') {
- state.video.isPaused = !state.video.isPaused
+ state.temp.video.isPaused = !state.temp.video.isPaused
update()
}
if (action === 'playbackJump') {
- state.video.jumpToTime = args[0] /* seconds */
+ state.temp.video.jumpToTime = args[0] /* seconds */
update()
}
if (action === 'toggleFullScreen') {
@@ -193,14 +219,14 @@ electron.ipcRenderer.on('seed', function (e, files) {
})
electron.ipcRenderer.on('fullscreenChanged', function (e, isFullScreen) {
- state.view.isFullScreen = isFullScreen
+ state.temp.isFullScreen = isFullScreen
update()
})
electron.ipcRenderer.on('addFakeDevice', function (e, device) {
var player = new EventEmitter()
player.play = (networkURL) => console.log(networkURL)
- state.view.devices[device] = player
+ state.temp.devices[device] = player
update()
})
@@ -237,9 +263,9 @@ function seed (files) {
function addTorrentEvents (torrent) {
torrent.on('infoHash', update)
torrent.on('done', function () {
- if (!state.view.isFocused) {
- state.view.dock.badge += 1
- electron.ipcRenderer.send('setBadge', state.view.dock.badge)
+ if (!state.temp.isFocused) {
+ state.temp.dock.badge += 1
+ electron.ipcRenderer.send('setBadge', state.temp.dock.badge)
}
update()
})
@@ -261,19 +287,19 @@ function torrentReady (torrent) {
}
function startServer (torrent, cb) {
- if (state.server) return cb()
+ if (state.temp.server) return cb()
// use largest file
- state.view.torrentPlaying = torrent.files.reduce(function (a, b) {
+ state.temp.torrentPlaying = torrent.files.reduce(function (a, b) {
return a.length > b.length ? a : b
})
- var index = torrent.files.indexOf(state.view.torrentPlaying)
+ var index = torrent.files.indexOf(state.temp.torrentPlaying)
var server = torrent.createServer()
server.listen(0, function () {
var port = server.address().port
var urlSuffix = ':' + port + '/' + index
- state.server = {
+ state.temp.server = {
server: server,
localURL: 'http://localhost' + urlSuffix,
networkURL: 'http://' + networkAddress() + urlSuffix
@@ -283,13 +309,13 @@ function startServer (torrent, cb) {
}
function closeServer () {
- state.server.server.destroy()
- state.server = null
+ state.temp.server.server.destroy()
+ state.temp.server = null
}
function openPlayer (torrent) {
startServer(torrent, function () {
- state.view.url = '/player'
+ state.temp.url = '/player'
update()
})
}
@@ -300,8 +326,10 @@ function deleteTorrent (torrent) {
function openChromecast (torrent) {
startServer(torrent, function () {
- state.view.devices.chromecast.play(state.server.networkURL, { title: 'WebTorrent — ' + torrent.name })
- state.view.devices.chromecast.on('error', function (err) {
+ state.temp.devices.chromecast.play(state.temp.server.networkURL, {
+ title: 'WebTorrent — ' + torrent.name
+ })
+ state.temp.devices.chromecast.on('error', function (err) {
err.message = 'Chromecast: ' + err.message
onError(err)
})
@@ -311,14 +339,15 @@ function openChromecast (torrent) {
function openAirplay (torrent) {
startServer(torrent, function () {
- state.view.devices.airplay.play(state.server.networkURL, 0, function () {})
- // TODO: handle airplay errors
+ state.temp.devices.airplay.play(state.temp.server.networkURL, 0, function () {
+ // TODO: handle airplay errors
+ })
update()
})
}
function setDimensions (dimensions) {
- state.view.mainWindowBounds = electron.remote.getCurrentWindow().getBounds()
+ state.temp.mainWindowBounds = electron.remote.getCurrentWindow().getBounds()
// Limit window size to screen size
var workAreaSize = electron.remote.screen.getPrimaryDisplay().workAreaSize
@@ -344,8 +373,8 @@ function setDimensions (dimensions) {
function restoreBounds () {
electron.ipcRenderer.send('setAspectRatio', 0)
- if (state.view.mainWindowBounds) {
- electron.ipcRenderer.send('setBounds', state.view.mainWindowBounds, true)
+ if (state.temp.mainWindowBounds) {
+ electron.ipcRenderer.send('setBounds', state.temp.mainWindowBounds, true)
}
}
diff --git a/renderer/views/app.js b/renderer/views/app.js
index 3315df8c..25334a66 100644
--- a/renderer/views/app.js
+++ b/renderer/views/app.js
@@ -10,9 +10,9 @@ var TorrentList = require('./torrent-list')
function App (state, dispatch) {
function getView () {
- if (state.view.url === '/') {
+ if (state.temp.url === '/') {
return TorrentList(state, dispatch)
- } else if (state.view.url === '/player') {
+ } else if (state.temp.url === '/player') {
return Player(state, dispatch)
}
}
@@ -20,8 +20,8 @@ function App (state, dispatch) {
// Show the header only when we're outside of fullscreen
// Also don't show it in the video player except in OSX
var isOSX = process.platform === 'darwin'
- var isVideo = state.view.url === '/player'
- var isFullScreen = state.view.isFullScreen
+ var isVideo = state.temp.url === '/player'
+ var isFullScreen = state.temp.isFullScreen
var header = !isFullScreen && (!isVideo || isOSX) ? Header(state, dispatch) : null
return hx`
diff --git a/renderer/views/header.js b/renderer/views/header.js
index 77f54099..e4ec6a26 100644
--- a/renderer/views/header.js
+++ b/renderer/views/header.js
@@ -23,12 +23,12 @@ function Header (state, dispatch) {
function getTitle () {
if (process.platform === 'darwin') {
- return hx`
${state.view.title}
`
+ return hx`${state.temp.title}
`
}
}
function plusButton () {
- if (state.view.url !== '/player') {
+ if (state.temp.url !== '/player') {
return hx`add`
}
}
diff --git a/renderer/views/player.js b/renderer/views/player.js
index 8757476b..966c0e77 100644
--- a/renderer/views/player.js
+++ b/renderer/views/player.js
@@ -3,25 +3,24 @@ module.exports = Player
var h = require('virtual-dom/h')
var hyperx = require('hyperx')
var hx = hyperx(h)
-var electron = require('electron')
function Player (state, dispatch) {
// Unfortunately, play/pause can't be done just by modifying HTML.
// Instead, grab the DOM node and play/pause it if necessary
var videoElement = document.querySelector('video')
if (videoElement !== null) {
- if (state.video.isPaused && !videoElement.paused) {
+ if (state.temp.video.isPaused && !videoElement.paused) {
videoElement.pause()
- } else if (!state.video.isPaused && videoElement.paused) {
+ } else if (!state.temp.video.isPaused && videoElement.paused) {
videoElement.play()
}
// When the user clicks or drags on the progress bar, jump to that position
- if (state.video.jumpToTime) {
- videoElement.currentTime = state.video.jumpToTime
- state.video.jumpToTime = null
+ if (state.temp.video.jumpToTime) {
+ videoElement.currentTime = state.temp.video.jumpToTime
+ state.temp.video.jumpToTime = null
}
- state.video.currentTime = videoElement.currentTime
- state.video.duration = videoElement.duration
+ state.temp.video.currentTime = videoElement.currentTime
+ state.temp.video.duration = videoElement.duration
}
// Show the video as large as will fit in the window, play immediately
@@ -29,7 +28,7 @@ function Player (state, dispatch) {
@@ -53,9 +52,9 @@ function Player (state, dispatch) {
// Renders all video controls: play/pause, scrub, loading bar
// TODO: cast buttons
function renderPlayerControls (state, dispatch) {
- var positionPercent = 100 * state.video.currentTime / state.video.duration
+ var positionPercent = 100 * state.temp.video.currentTime / state.temp.video.duration
var playbackCursorStyle = { left: 'calc(' + positionPercent + '% - 8px)' }
- var torrent = state.view.torrentPlaying._torrent
+ var torrent = state.temp.torrentPlaying._torrent
var elements = [
hx`
@@ -76,7 +75,7 @@ function renderPlayerControls (state, dispatch) {
`
]
// If we've detected a Chromecast or AppleTV, the user can play video there
- if (state.view.devices.chromecast) {
+ if (state.temp.devices.chromecast) {
elements.push(hx`
dispatch('openChromecast', torrent)}>
@@ -84,7 +83,7 @@ function renderPlayerControls (state, dispatch) {
`)
}
- if (state.view.devices.airplay) {
+ if (state.temp.devices.airplay) {
elements.push(hx`
dispatch('openAirplay', torrent)}>
@@ -104,7 +103,7 @@ function renderPlayerControls (state, dispatch) {
}
elements.push(hx`
dispatch('playPause')}>
- ${state.video.isPaused ? 'play_arrow' : 'pause'}
+ ${state.temp.video.isPaused ? 'play_arrow' : 'pause'}
`)
@@ -112,11 +111,9 @@ function renderPlayerControls (state, dispatch) {
// Handles a click or drag to scrub (jump to another position in the video)
function handleScrub (e) {
- // TODO: don't use remote -- it does IPC with the main process which is overkill
- // just to get the width
- var windowWidth = electron.remote.getCurrentWindow().getBounds().width
+ var windowWidth = document.querySelector('body').clientWidth
var fraction = e.clientX / windowWidth
- var position = fraction * state.video.duration /* seconds */
+ var position = fraction * state.temp.video.duration /* seconds */
dispatch('playbackJump', position)
}
}
@@ -124,7 +121,7 @@ function renderPlayerControls (state, dispatch) {
// Renders the loading bar. Shows which parts of the torrent are loaded, which
// can be "spongey" / non-contiguous
function renderLoadingBar (state) {
- var torrent = state.view.torrentPlaying._torrent
+ var torrent = state.temp.torrentPlaying._torrent
if (torrent === null) {
return []
}
diff --git a/renderer/views/torrent-list.js b/renderer/views/torrent-list.js
index 35cc5943..afbdd193 100644
--- a/renderer/views/torrent-list.js
+++ b/renderer/views/torrent-list.js
@@ -6,18 +6,18 @@ var hx = hyperx(h)
var prettyBytes = require('pretty-bytes')
function TorrentList (state, dispatch) {
- var torrents = state.view.client
- ? state.view.client.torrents
+ var torrents = state.temp.client
+ ? state.temp.client.torrents
: []
- var list = torrents.map((torrent) => renderTorrent(state, dispatch, torrent))
+ var list = torrents.map((torrent) => renderTorrent(torrent, dispatch))
return hx`${list}
`
}
// Renders a torrent in the torrent list
// Includes name, download status, play button, background image
// May be expanded for additional info, including the list of files inside
-function renderTorrent (state, dispatch, torrent) {
+function renderTorrent (torrent, dispatch) {
// Background image: show some nice visuals, like a frame from the movie, if possible
var style = {}
if (torrent.posterURL) {
diff --git a/resources/BigBuckBunny_archive.torrent b/resources/BigBuckBunny_archive.torrent
new file mode 100644
index 00000000..47c79c2f
Binary files /dev/null and b/resources/BigBuckBunny_archive.torrent differ
diff --git a/resources/ElephantsDream_archive.torrent b/resources/ElephantsDream_archive.torrent
new file mode 100644
index 00000000..e427d051
--- /dev/null
+++ b/resources/ElephantsDream_archive.torrent
@@ -0,0 +1,520 @@
+d8:announce36:http://bt1.archive.org:6969/announce13:announce-listll36:http://bt1.archive.org:6969/announceel36:http://bt2.archive.org:6969/announceee7:comment612:This content hosted at the Internet Archive at http://archive.org/details/ElephantsDream
+Files may have changed, which prevents torrents from downloading correctly or completely; please check for an updated torrent at http://archive.org/download/ElephantsDream/ElephantsDream_archive.torrent
+Note: retrieval usually requires a client that supports webseeding (GetRight style).
+Note: many Internet Archive torrents contain a 'pad file' directory. This directory and the files within it may be erased once retrieval completes.
+Note: the file ElephantsDream_meta.xml contains metadata about this torrent's contents.10:created by15:ia_make_torrent13:creation datei1363382100e4:infod5:filesld5:crc328:78394d986:lengthi22273863680e3:md532:f04754ab71fa36815ac8be82a4f7acf95:mtime10:12151404594:pathl15:ED-1080-png.tare4:sha140:540135fd3eba0a0f863d273357fa17f650d93eaded4:attr1:p6:lengthi2084864e4:pathl17:.____padding_file1:0eed5:crc328:df8166696:lengthi3569858560e3:md532:0a876f9f985494e31a6f07e8aba4e6665:mtime10:12150904934:pathl14:ED-360-png.tare4:sha140:8f7560d5d889fbacc83cd25c3d1c232ec5e6aa38ed4:attr1:p6:lengthi3688448e4:pathl17:.____padding_file1:1eed5:crc328:bb13a9406:lengthi51785853e3:md532:c44617e6a8a838ae48e4594e304ee2635:mtime10:12151353084:pathl20:ED-CM-5.1-DVD-C.flace4:sha140:cfb9202c2349be442995583d5a0f975842eec1abed4:attr1:p6:lengthi2740099e4:pathl17:.____padding_file1:2eed5:crc328:7bdc6b886:lengthi4635596e3:md532:878c767b353b128808b214fa6c57f6ee5:mtime10:12151528754:pathl19:ED-CM-5.1-DVD-C.ogge4:sha140:b2c917b697f5f9be580b7389b2c55439f483b189ed4:attr1:p6:lengthi3753012e4:pathl17:.____padding_file1:3eed5:crc328:231a55346:lengthi5265158e3:md532:c7729a1067944a439f77ff881bdc6b975:mtime10:12151530284:pathl24:ED-CM-5.1-DVD-C_64kb.mp3e4:sha140:07ec30dbe0eeee02dda79de3553d579eddbdc899ed4:attr1:p6:lengthi3123450e4:pathl17:.____padding_file1:4eed5:crc328:4f394a2c6:lengthi7203512e3:md532:c85bc34afcb177f56bbed5088337840a5:mtime10:12151529874:pathl23:ED-CM-5.1-DVD-C_vbr.mp3e4:sha140:3e4afbf505cc8033c654b2ad4d3b97e69d30107fed4:attr1:p6:lengthi1185096e4:pathl17:.____padding_file1:5eed5:crc328:f910f5876:lengthi58521345e3:md532:1b5b1dd2f2d3d9356a7fd2cfe556f1445:mtime10:12151353184:pathl20:ED-CM-5.1-DVD-L.flace4:sha140:966a37eb6dc6ad3d553c0b729720ed8afa6d62eaed4:attr1:p6:lengthi198911e4:pathl17:.____padding_file1:6eed5:crc328:778a01a46:lengthi5230715e3:md532:ea45353ab74527db4539b1b933ae2e315:mtime10:12151535544:pathl19:ED-CM-5.1-DVD-L.ogge4:sha140:228e509772a890322084877be46bafa038721201ed4:attr1:p6:lengthi3157893e4:pathl17:.____padding_file1:7eed5:crc328:8d8da7396:lengthi13128700e3:md532:54424b0b42ee22d6fc04b02a3788f09f5:mtime10:12151353214:pathl22:ED-CM-5.1-DVD-LFE.flace4:sha140:3de32a9ad9af8f29d9e18ae23ccef55b3a73314fed4:attr1:p6:lengthi3648516e4:pathl17:.____padding_file1:8eed5:crc328:9d2d92686:lengthi473422e3:md532:8957dac5cb7c74e93eccf8e35b165c0f5:mtime10:12151533464:pathl21:ED-CM-5.1-DVD-LFE.ogge4:sha140:b3ba58d0ad446174eb9abe864c343aa4f574f93fed4:attr1:p6:lengthi3720882e4:pathl17:.____padding_file1:9eed5:crc328:ce7a86c86:lengthi5265158e3:md532:f310685368029d8f8921680b536f551d5:mtime10:12151528174:pathl26:ED-CM-5.1-DVD-LFE_64kb.mp3e4:sha140:e8bf79642c1796a3189ac92a883aa1091316300eed4:attr1:p6:lengthi3123450e4:pathl17:.____padding_file2:10eed5:crc328:b97c728a6:lengthi2633024e3:md532:e7f0ca754a81a1538fd1d4245c1b88585:mtime10:12151531824:pathl25:ED-CM-5.1-DVD-LFE_vbr.mp3e4:sha140:03500e00d39e0b7ed9f1fc9570786e20fbe8104bed4:attr1:p6:lengthi1561280e4:pathl17:.____padding_file2:11eed5:crc328:574a55f86:lengthi40961833e3:md532:0e588d4cb5e7608834769321a6d43d625:mtime10:12151353294:pathl21:ED-CM-5.1-DVD-LS.flace4:sha140:6f96312ce41c70c9c2b337f8e7fd7bc48c247faeed4:attr1:p6:lengthi981207e4:pathl17:.____padding_file2:12eed5:crc328:75def9966:lengthi4014251e3:md532:b4842814ff3cde5408a5985ae9d554225:mtime10:12151530854:pathl20:ED-CM-5.1-DVD-LS.ogge4:sha140:22fec43042aa536cbed348c75a7a61ca5c602919ed4:attr1:p6:lengthi180053e4:pathl17:.____padding_file2:13eed5:crc327:571a4ad6:lengthi5265158e3:md532:a646a20f47f560b6e31efde46ae1616b5:mtime10:12151527944:pathl25:ED-CM-5.1-DVD-LS_64kb.mp3e4:sha140:c4c7fd2010af3efe1ceb73f821f7701065570b56ed4:attr1:p6:lengthi3123450e4:pathl17:.____padding_file2:14eed5:crc328:75419f5b6:lengthi6133304e3:md532:ddc6072a77b2451b20a9947bce3516565:mtime10:12151534594:pathl24:ED-CM-5.1-DVD-LS_vbr.mp3e4:sha140:4533f10a84655105611352f3d5c1ce08e52d2650ed4:attr1:p6:lengthi2255304e4:pathl17:.____padding_file2:15eed5:crc328:a88416956:lengthi5265158e3:md532:2496de47449e6ea7940e1e102361d6925:mtime10:12151535014:pathl24:ED-CM-5.1-DVD-L_64kb.mp3e4:sha140:a6405cb2151aeb515984577dcb963f9623de440ced4:attr1:p6:lengthi3123450e4:pathl17:.____padding_file2:16eed5:crc328:dead090d6:lengthi8076488e3:md532:b8d718041869b0ea63e5d89fe516f3fc5:mtime10:12151532984:pathl23:ED-CM-5.1-DVD-L_vbr.mp3e4:sha140:27005dd5b05f515c8f39abf7714b733ae9c2fe1fed4:attr1:p6:lengthi312120e4:pathl17:.____padding_file2:17eed5:crc328:6f14fcf16:lengthi58303103e3:md532:be942a5b810010a2cf9e1b6f831fef225:mtime10:12151356474:pathl20:ED-CM-5.1-DVD-R.flace4:sha140:bc25f8480234968dbbe057d0fe3c2dbcd5928ff5ed4:attr1:p6:lengthi417153e4:pathl17:.____padding_file2:18eed5:crc328:5487219e6:lengthi5261503e3:md532:aff608b31c881f94b045c4c5f041841a5:mtime10:12151520234:pathl19:ED-CM-5.1-DVD-R.ogge4:sha140:7bec8af3034fb735db7e4db1e7450e0012dae99ced4:attr1:p6:lengthi3127105e4:pathl17:.____padding_file2:19eed5:crc328:bcfbab286:lengthi40383772e3:md532:3cb0215ce6c7452e9c93f40a82e287d75:mtime10:12151356554:pathl21:ED-CM-5.1-DVD-RS.flace4:sha140:f92426eb05909c1212ffa15305642769ab46534fed4:attr1:p6:lengthi1559268e4:pathl17:.____padding_file2:20eed5:crc328:5e4f42aa6:lengthi3995270e3:md532:066eb4bcc627914d4d2ad852ae67a64d5:mtime10:12151527534:pathl20:ED-CM-5.1-DVD-RS.ogge4:sha140:5d2926d9048e6972dd5c589647d0be46b0081b5ced4:attr1:p6:lengthi199034e4:pathl17:.____padding_file2:21eed5:crc328:3aacccc06:lengthi5265158e3:md532:7ca385468f8ae1c60b2340c428a829855:mtime10:12151519594:pathl25:ED-CM-5.1-DVD-RS_64kb.mp3e4:sha140:a589897d823b7d3eab11de3ac2d3f816721eb08eed4:attr1:p6:lengthi3123450e4:pathl17:.____padding_file2:22eed5:crc328:1dcce8456:lengthi6051848e3:md532:375c0255fdc8e2dd777172a217a47c8f5:mtime10:12151521244:pathl24:ED-CM-5.1-DVD-RS_vbr.mp3e4:sha140:d99d6bc9893f02bf999991ec6a46209bd6595ecded4:attr1:p6:lengthi2336760e4:pathl17:.____padding_file2:23eed5:crc328:77675d6a6:lengthi5265158e3:md532:957ffa457a8b6e4da88ea2a46f3b36d35:mtime10:12151523964:pathl24:ED-CM-5.1-DVD-R_64kb.mp3e4:sha140:04c4cfa6cd3036a1e34130c7417ff236680ad045ed4:attr1:p6:lengthi3123450e4:pathl17:.____padding_file2:24eed5:crc328:9b98ddfe6:lengthi8060192e3:md532:90b535ae6b365b332379d9ced70b61045:mtime10:12151522104:pathl23:ED-CM-5.1-DVD-R_vbr.mp3e4:sha140:1009f2385a6508fb03ae0b6a39c72958585126e9ed4:attr1:p6:lengthi328416e4:pathl17:.____padding_file2:25eed5:crc328:9354fad06:lengthi59166432e3:md532:90f3ed5b44465bd1ec867221b2420e4f5:mtime10:12151356674:pathl19:ED-CM-St-16bit.flace4:sha140:74ea3edf28d18dd90374f96b5f83d994ff1307baed4:attr1:p6:lengthi3748128e4:pathl17:.____padding_file2:26eed5:crc328:e623d0476:lengthi8432933e3:md532:837eb222009319a20c28f02f77223de65:mtime10:12151523344:pathl18:ED-CM-St-16bit.ogge4:sha140:335b38db5225c0b6a43d891fc89e41a7eeb31bdaed4:attr1:p6:lengthi4149979e4:pathl17:.____padding_file2:27eed5:crc328:1f6d6e096:lengthi5267248e3:md532:4508479df8379a3220e69d4fbe36e74e5:mtime10:12151527074:pathl23:ED-CM-St-16bit_64kb.mp3e4:sha140:7858a4387b58f39e23a7209f7f1cecfde8fe58d5ed4:attr1:p6:lengthi3121360e4:pathl17:.____padding_file2:28eed5:crc328:eb6e90ed6:lengthi14518280e3:md532:18063c60533d4908e120ff7b183171c85:mtime10:12151526054:pathl22:ED-CM-St-16bit_vbr.mp3e4:sha140:94543164aececc3be58ebab02bf4cc719545efc6ed4:attr1:p6:lengthi2258936e4:pathl17:.____padding_file2:29eed5:crc327:89f2e016:lengthi381e3:md532:f8bb3ab0e4fc9e132d2837c8d52a18f55:mtime10:12151535694:pathl18:ElephantsDream.ffpe4:sha140:dc31d26a0a8bb59f0883a2658b30dcf9204ecf3eed4:attr1:p6:lengthi4193923e4:pathl17:.____padding_file2:30eed5:crc328:b45a485b6:lengthi388e3:md532:60da7af22c1c508faa606c0ca54f8b105:mtime10:12151535694:pathl18:ElephantsDream.md5e4:sha140:45e02516a5b954ce83d4edbd6e3e857f93483455ed4:attr1:p6:lengthi4193916e4:pathl17:.____padding_file2:31eed5:crc328:1506a6b16:lengthi507e3:md532:b1505e2fa4c9d783ab20b0453bae395a5:mtime10:12151536114:pathl23:ElephantsDream_64kb.m3ue4:sha140:dcec1af152a84141876b1052f621e3a681ae7e85ed4:attr1:p6:lengthi4193797e4:pathl17:.____padding_file2:32eed5:crc328:9ecf0b056:lengthi5727e3:md532:92b07beda3b3136d572f747b3ffd5b6d5:mtime10:12998459284:pathl23:ElephantsDream_meta.xmle4:sha140:97ec1c5a2b14241f36b8c97a0de9f07b2e810258ed4:attr1:p6:lengthi4188577e4:pathl17:.____padding_file2:33eed5:crc328:644494a96:lengthi500e3:md532:b1dde785140c5085bec3af8cb49c0e0a5:mtime10:12151536124:pathl22:ElephantsDream_vbr.m3ue4:sha140:f17abbd9a815758d42539bdb8e43d145603f64b3ed4:attr1:p6:lengthi4193804e4:pathl17:.____padding_file2:34eed5:crc328:1a83f6e66:lengthi445866736e3:md532:f448ac0df7522142151d02cd4d64eaee5:mtime10:12150539564:pathl11:ed_1024.avie4:sha140:811f5316bb024285f275654e15ce3bd1afd9ca02ed4:attr1:p6:lengthi2923792e4:pathl17:.____padding_file2:35eed5:crc328:13e6dc926:lengthi232318e3:md532:82f96c51ec69523421a55fbaf362e0105:mtime10:12275017044:pathl11:ed_1024.gife4:sha140:5b6e33817139cfd3f5e877d0f08773a34d8f3097ed4:attr1:p6:lengthi3961986e4:pathl17:.____padding_file2:36eed5:crc328:9928cf146:lengthi45617852e3:md532:d3f97928fbd82d198abd07636d3fb9885:mtime10:12275022114:pathl11:ed_1024.ogve4:sha140:49d634c327a9a4dc156bd8150475eed0b6450087ed4:attr1:p6:lengthi519492e4:pathl17:.____padding_file2:37eed5:crc328:5d048d7b6:lengthi47065346e3:md532:841fdeef3a2bb80b34cf5a25d990a96e5:mtime10:12275016074:pathl17:ed_1024_512kb.mp4e4:sha140:29b530c0f90eb259014b6004dfbb2788481df6deed4:attr1:p6:lengthi3266302e4:pathl17:.____padding_file2:38eed5:crc328:489b1e446:lengthi854537054e3:md532:9cdec69258b03232cc935c463d9356da5:mtime10:12150545024:pathl9:ed_hd.avie4:sha140:5cea10562b8e4a434f1562d6410424746d721537ed4:attr1:p6:lengthi1100962e4:pathl17:.____padding_file2:39eed5:crc328:2c166f846:lengthi231015e3:md532:cc8c19f0170cfb3b3df698e289ac3eab5:mtime10:12274987444:pathl9:ed_hd.gife4:sha140:e2ebb3ebebff72e59a6ffbd52b57855bbe1e1227ed4:attr1:p6:lengthi3963289e4:pathl17:.____padding_file2:40eed5:crc328:aa3d70f76:lengthi45657884e3:md532:0beb0aff7069f258cc870dc5853162ec5:mtime10:12275000524:pathl9:ed_hd.ogve4:sha140:78b43a8dcbea1e41f4f38154a985fff313bc43b3ed4:attr1:p6:lengthi479460e4:pathl17:.____padding_file2:41eed5:crc328:161663946:lengthi47311688e3:md532:00d3459c83909edd3fd0d513108164c45:mtime10:12274985074:pathl15:ed_hd_512kb.mp4e4:sha140:cc7a8083b49a4c6030922ff913b0cccefef7cda7ed4:attr1:p6:lengthi3019960e4:pathl17:.____padding_file2:42eee4:name14:ElephantsDream12:piece lengthi4194304e6:pieces132960:k붰qb&1wm/@qFqba;f; teZr;cXEd6]mu1i1FR-Ufyez@۾*[<lظӰPeZ\cˑ5}w 0\â,|NDrlJ/zT7z<8oT/xA6m{<9ے;'bn˗4C~Ad3g]Is6:RDa0=5*:H^D'>'Li jK/'!hIxaO?k ׃#e
'`^dOb8ءiZ-' $0rR
+IֽD=:+_}r_`v8ПyO2&