diff --git a/package.json b/package.json
index 919cd38652bc8a3b306d8015088c921117d617a1..e892dc02d54e550a3a37db108b3ee764a94282c9 100644
--- a/package.json
+++ b/package.json
@@ -45,9 +45,11 @@
     "opt-cli": "1.5.1",
     "react": "15.x.x",
     "react-dom": "15.x.x",
+    "react-modal": "1.5.2",
     "scratch-blocks": "latest",
     "scratch-render": "latest",
     "scratch-vm": "latest",
+    "svg-to-image": "1.1.3",
     "travis-after-all": "jamesarosen/travis-after-all#override-api-urls",
     "webpack": "1.13.2",
     "webpack-dev-server": "1.15.2",
diff --git a/src/components/costume-canvas.js b/src/components/costume-canvas.js
new file mode 100644
index 0000000000000000000000000000000000000000..368d52a9be70056ed9b4f6ad6df427e80289b4eb
--- /dev/null
+++ b/src/components/costume-canvas.js
@@ -0,0 +1,117 @@
+const React = require('react');
+const svgToImage = require('svg-to-image');
+const xhr = require('xhr');
+
+/**
+ * @fileoverview
+ * A component for rendering Scratch costume URLs to canvases.
+ * Use for sprite library, costume library, sprite selector, etc.
+ * Props include width, height, and direction (direction in Scratch value).
+ */
+
+class CostumeCanvas extends React.Component {
+    componentDidMount () {
+        this.load();
+    }
+    componentDidUpdate (prevProps) {
+        if (prevProps.url !== this.props.url) {
+            this.load();
+        } else {
+            if (prevProps.width !== this.props.width ||
+                prevProps.height !== this.props.height ||
+                prevProps.direction !== this.props.direction) {
+                this.draw();
+            }
+        }
+    }
+    draw () {
+        if (!this.refs.costumeCanvas) {
+            return;
+        }
+        // Draw the costume to the rendered canvas.
+        const img = this.img;
+        const context = this.refs.costumeCanvas.getContext('2d');
+        // Scale to fit.
+        let scale;
+        // Choose the larger dimension to scale by.
+        if (img.width > img.height) {
+            scale = this.refs.costumeCanvas.width / img.width;
+        } else {
+            scale = this.refs.costumeCanvas.height / img.height;
+        }
+        // Rotate by the Scratch-value direction.
+        const angle = (-90 + this.props.direction) * Math.PI / 180;
+        // Rotation origin point will be center of the canvas.
+        const contextTranslateX = this.refs.costumeCanvas.width / 2;
+        const contextTranslateY = this.refs.costumeCanvas.height / 2;
+        // First, clear the canvas.
+        context.clearRect(0, 0,
+            this.refs.costumeCanvas.width, this.refs.costumeCanvas.height);
+        // Translate the context to the center of the canvas,
+        // then rotate canvas drawing by `angle`.
+        context.translate(contextTranslateX, contextTranslateY);
+        context.rotate(angle);
+        context.drawImage(img,
+            0, 0, img.width, img.height,
+            -(scale * img.width / 2),  -(scale * img.height / 2),
+            scale * img.width,
+            scale * img.height);
+        // Reset the canvas rotation and translation to 0, (0, 0).
+        context.rotate(-angle);
+        context.translate(-contextTranslateX, -contextTranslateY);
+    }
+    load () {
+        // Draw the icon on our canvas.
+        const url = this.props.url;
+        if (url.indexOf('.svg') > -1) {
+            // Vector graphics: need to download with XDR and rasterize.
+            // Queue request asynchronously.
+            setTimeout(() => {
+                xhr.get({
+                    useXDR: true,
+                    url: url
+                }, (err, response, body) => {
+                    if (!err) {
+                        svgToImage(body, (err, img) => {
+                            if (!err) {
+                                this.img = img;
+                                this.draw();
+                            }
+                        });
+                    }
+                });
+            }, 0);
+
+        } else {
+            // Raster graphics: create Image and draw it.
+            let img = new Image();
+            img.src = url;
+            img.onload = () => {
+                this.img = img;
+                this.draw();
+            };
+        }
+    }
+    render () {
+        return <canvas
+            ref='costumeCanvas'
+            width={this.props.width}
+            height={this.props.height}
+        />;
+    }
+}
+
+CostumeCanvas.defaultProps = {
+    width: 100,
+    height: 100,
+    direction: 90
+};
+
+CostumeCanvas.propTypes = {
+    url: React.PropTypes.string,
+    width: React.PropTypes.number,
+    height: React.PropTypes.number,
+    direction: React.PropTypes.number
+};
+
+module.exports = CostumeCanvas;
diff --git a/src/components/library-item.js b/src/components/library-item.js
new file mode 100644
index 0000000000000000000000000000000000000000..39f65596a34524e88f8abdaf9758b65b90f70b14
--- /dev/null
+++ b/src/components/library-item.js
@@ -0,0 +1,49 @@
+const React = require('react');
+
+const CostumeCanvas = require('./costume-canvas');
+
+class LibraryItem extends React.Component {
+    render () {
+        let style = (this.props.selected) ?
+            this.props.selectedGridTileStyle : this.props.gridTileStyle;
+        return (
+            <div style={style} onClick={() => this.props.onSelect(this.props.id)}>
+                <CostumeCanvas url={this.props.iconURL} />
+                <p>{this.props.name}</p>
+            </div>
+        );
+    }
+}
+
+LibraryItem.defaultProps = {
+    gridTileStyle: {
+        float: 'left',
+        width: '140px',
+        marginLeft: '5px',
+        marginRight: '5px',
+        textAlign: 'center',
+        cursor: 'pointer'
+    },
+    selectedGridTileStyle: {
+        float: 'left',
+        width: '140px',
+        marginLeft: '5px',
+        marginRight: '5px',
+        textAlign: 'center',
+        cursor: 'pointer',
+        background: '#aaa',
+        borderRadius: '6px'
+    }
+};
+
+LibraryItem.propTypes = {
+    name: React.PropTypes.string,
+    iconURL: React.PropTypes.string,
+    gridTileStyle: React.PropTypes.object,
+    selectedGridTileStyle: React.PropTypes.object,
+    selected: React.PropTypes.bool,
+    onSelect: React.PropTypes.func,
+    id: React.PropTypes.number
+};
+
+module.exports = LibraryItem;
diff --git a/src/components/library.js b/src/components/library.js
new file mode 100644
index 0000000000000000000000000000000000000000..f23f4b8267d18269ddc9627ece6ae25ff0ff3440
--- /dev/null
+++ b/src/components/library.js
@@ -0,0 +1,69 @@
+const bindAll = require('lodash.bindall');
+const React = require('react');
+
+const LibraryItem = require('./library-item');
+const ModalComponent = require('./modal');
+
+class LibraryComponent extends React.Component {
+    constructor (props) {
+        super(props);
+        bindAll(this, ['onSelect']);
+        this.state = {selectedItem: null};
+    }
+    onSelect (id) {
+        if (this.state.selectedItem == id) {
+            // Double select: select as the library's value.
+            this.props.onRequestClose();
+            this.props.onItemSelected(this.props.data[id]);
+        }
+        this.setState({selectedItem: id});
+    }
+    render () {
+        let itemId = 0;
+        let gridItems = this.props.data.map((dataItem) => {
+            let id = itemId;
+            itemId++;
+            const scratchURL = (dataItem.md5) ? 'https://cdn.assets.scratch.mit.edu/internalapi/asset/' +
+                dataItem.md5 + '/get/' : dataItem.rawURL;
+            return <LibraryItem
+                name={dataItem.name}
+                iconURL={scratchURL}
+                key={'item_' + id}
+                selected={this.state.selectedItem == id}
+                onSelect={this.onSelect}
+                id={id}
+            />;
+        });
+
+        const scrollGridStyle = {
+            overflow: 'scroll',
+            position: 'absolute',
+            top: '70px',
+            bottom: '20px',
+            left: '30px',
+            right: '30px'
+        };
+
+        return (
+            <ModalComponent
+                onRequestClose={this.props.onRequestClose}
+                visible={this.props.visible}
+            >
+                <h1>{this.props.title}</h1>
+                <div style={scrollGridStyle}>
+                    {gridItems}
+                </div>
+            </ModalComponent>
+        );
+    }
+}
+
+LibraryComponent.propTypes = {
+    title: React.PropTypes.string,
+    data: React.PropTypes.array,
+    visible: React.PropTypes.bool,
+    onRequestClose: React.PropTypes.func,
+    onItemSelected: React.PropTypes.func
+};
+
+module.exports = LibraryComponent;
diff --git a/src/components/modal.js b/src/components/modal.js
new file mode 100644
index 0000000000000000000000000000000000000000..a2c25714964efa80c0a5f5580e12b360ac050844
--- /dev/null
+++ b/src/components/modal.js
@@ -0,0 +1,70 @@
+const React = require('react');
+const ReactModal = require('react-modal');
+
+class ModalComponent extends React.Component {
+    render () {
+        return (
+            <ReactModal
+                ref="modal"
+                style={this.props.modalStyle}
+                isOpen={this.props.visible}
+                onRequestClose={this.props.onRequestClose}
+            >
+                <div
+                    onClick={this.props.onRequestClose}
+                    style={this.props.closeButtonStyle}
+                >
+                    x
+                </div>
+                {this.props.children}
+            </ReactModal>
+        );
+    }
+}
+
+const modalStyle = {
+    overlay: {
+        zIndex: 1000,
+        backgroundColor: 'rgba(0, 0, 0, .75)'
+    },
+    content: {
+        position: 'absolute',
+        overflow: 'visible',
+        borderRadius: '6px',
+        padding: 0,
+        top: '5%',
+        bottom: '5%',
+        left: '5%',
+        right: '5%',
+        background: '#fcfcfc'
+    }
+};
+
+const closeButtonStyle = {
+    color: 'rgb(255, 255, 255)',
+    background: 'rgb(50, 50, 50)',
+    borderRadius: '15px',
+    width: '30px',
+    height: '25px',
+    textAlign: 'center',
+    paddingTop: '5px',
+    position: 'absolute',
+    right: '3px',
+    top: '3px',
+    cursor: 'pointer'
+};
+
+ModalComponent.defaultProps = {
+    modalStyle: modalStyle,
+    closeButtonStyle: closeButtonStyle
+};
+
+ModalComponent.propTypes = {
+    children: React.PropTypes.node,
+    modalStyle: React.PropTypes.object,
+    closeButtonStyle: React.PropTypes.object,
+    onRequestClose: React.PropTypes.func,
+    visible: React.PropTypes.bool
+};
+
+module.exports = ModalComponent;
diff --git a/src/components/sprite-selector.js b/src/components/sprite-selector.js
index a37213d9b25db729ea3736eab486ee28f942bc98..da00622bf8f21b0c8d3b2cb57576af7d3015eb91 100644
--- a/src/components/sprite-selector.js
+++ b/src/components/sprite-selector.js
@@ -6,6 +6,9 @@ class SpriteSelectorComponent extends React.Component {
             onChange,
             sprites,
             value,
+            openNewSprite,
+            openNewCostume,
+            openNewBackdrop,
             ...props
         } = this.props;
         return (
@@ -28,6 +31,11 @@ class SpriteSelectorComponent extends React.Component {
                         </option>
                     ))}
                 </select>
+                <p>
+                    <button onClick={openNewSprite}>New sprite</button>
+                    <button onClick={openNewCostume}>New costume</button>
+                    <button onClick={openNewBackdrop}>New backdrop</button>
+                </p>
             </div>
         );
     }
@@ -41,7 +49,10 @@ SpriteSelectorComponent.propTypes = {
             name: React.PropTypes.string
         })
     ),
-    value: React.PropTypes.arrayOf(React.PropTypes.string)
+    value: React.PropTypes.arrayOf(React.PropTypes.string),
+    openNewSprite: React.PropTypes.func,
+    openNewCostume: React.PropTypes.func,
+    openNewBackdrop: React.PropTypes.func
 };
 
 module.exports = SpriteSelectorComponent;
diff --git a/src/containers/backdrop-library.js b/src/containers/backdrop-library.js
new file mode 100644
index 0000000000000000000000000000000000000000..bcac13b5237e414dcf2174577075e7eff591394d
--- /dev/null
+++ b/src/containers/backdrop-library.js
@@ -0,0 +1,53 @@
+const bindAll = require('lodash.bindall');
+const React = require('react');
+const VM = require('scratch-vm');
+const MediaLibrary = require('../lib/media-library');
+
+const LibaryComponent = require('../components/library');
+
+
+class BackdropLibrary extends React.Component {
+    constructor (props) {
+        super(props);
+        bindAll(this, ['setData', 'selectItem']);
+        this.state = {backdropData: []};
+    }
+    componentWillReceiveProps (nextProps) {
+        if (nextProps.visible && this.state.backdropData.length === 0) {
+            this.props.mediaLibrary.getMediaLibrary('backdrop', this.setData);
+        }
+    }
+    setData (data) {
+        this.setState({backdropData: data});
+    }
+    selectItem (item) {
+        var vmBackdrop = {
+            skin: 'https://cdn.assets.scratch.mit.edu/internalapi/asset/' + item.md5 + '/get/',
+            name: item.name,
+            rotationCenterX: item.info[0],
+            rotationCenterY: item.info[1]
+        };
+        if (item.info.length > 2) {
+            vmBackdrop.bitmapResolution = item.info[2];
+        }
+        this.props.vm.addBackdrop(vmBackdrop);
+    }
+    render () {
+        return <LibaryComponent
+            title="Backdrop Library"
+            visible={this.props.visible}
+            data={this.state.backdropData}
+            onRequestClose={this.props.onRequestClose}
+            onItemSelected={this.selectItem}
+        />;
+    }
+}
+
+BackdropLibrary.propTypes = {
+    vm: React.PropTypes.instanceOf(VM).isRequired,
+    mediaLibrary: React.PropTypes.instanceOf(MediaLibrary),
+    visible: React.PropTypes.bool,
+    onRequestClose: React.PropTypes.func
+};
+
+module.exports = BackdropLibrary;
diff --git a/src/containers/costume-library.js b/src/containers/costume-library.js
new file mode 100644
index 0000000000000000000000000000000000000000..091a736ffbb6213b34d2402da3251d10e014c243
--- /dev/null
+++ b/src/containers/costume-library.js
@@ -0,0 +1,53 @@
+const bindAll = require('lodash.bindall');
+const React = require('react');
+const VM = require('scratch-vm');
+const MediaLibrary = require('../lib/media-library');
+
+const LibaryComponent = require('../components/library');
+
+
+class CostumeLibrary extends React.Component {
+    constructor (props) {
+        super(props);
+        bindAll(this, ['setData', 'selectItem']);
+        this.state = {costumeData: []};
+    }
+    componentWillReceiveProps (nextProps) {
+        if (nextProps.visible && this.state.costumeData.length === 0) {
+            this.props.mediaLibrary.getMediaLibrary('costume', this.setData);
+        }
+    }
+    setData (data) {
+        this.setState({costumeData: data});
+    }
+    selectItem (item) {
+        var vmCostume = {
+            skin: 'https://cdn.assets.scratch.mit.edu/internalapi/asset/' + item.md5 + '/get/',
+            name: item.name,
+            rotationCenterX: item.info[0],
+            rotationCenterY: item.info[1]
+        };
+        if (item.info.length > 2) {
+            vmCostume.bitmapResolution = item.info[2];
+        }
+        this.props.vm.addCostume(vmCostume);
+    }
+    render () {
+        return <LibaryComponent
+            title="Costume Library"
+            visible={this.props.visible}
+            data={this.state.costumeData}
+            onRequestClose={this.props.onRequestClose}
+            onItemSelected={this.selectItem}
+        />;
+    }
+}
+
+CostumeLibrary.propTypes = {
+    vm: React.PropTypes.instanceOf(VM).isRequired,
+    mediaLibrary: React.PropTypes.instanceOf(MediaLibrary),
+    visible: React.PropTypes.bool,
+    onRequestClose: React.PropTypes.func
+};
+
+module.exports = CostumeLibrary;
diff --git a/src/containers/gui.js b/src/containers/gui.js
index 937f4446f9851bd928e2ebc6dcde377f58f08cbf..0ab742034fa6ff94d4efc6942cf42e2a70a548d1 100644
--- a/src/containers/gui.js
+++ b/src/containers/gui.js
@@ -1,8 +1,10 @@
+const bindAll = require('lodash.bindall');
 const defaultsDeep = require('lodash.defaultsdeep');
 const React = require('react');
 const VM = require('scratch-vm');
 
 const VMManager = require('../lib/vm-manager');
+const MediaLibrary = require('../lib/media-library');
 
 const Blocks = require('./blocks');
 const GUIComponent = require('../components/gui');
@@ -11,10 +13,17 @@ const SpriteSelector = require('./sprite-selector');
 const Stage = require('./stage');
 const StopAll = require('./stop-all');
 
+const SpriteLibrary = require('./sprite-library');
+const CostumeLibrary = require('./costume-library');
+const BackdropLibrary = require('./backdrop-library');
+
 class GUI extends React.Component {
     constructor (props) {
         super(props);
+        bindAll(this, ['closeModal']);
         this.vmManager = new VMManager(this.props.vm);
+        this.mediaLibrary = new MediaLibrary();
+        this.state = {currentModal: null};
     }
     componentDidMount () {
         this.vmManager.attachKeyboardEvents();
@@ -30,12 +39,21 @@ class GUI extends React.Component {
             this.props.vm.loadProject(nextProps.projectData);
         }
     }
+    openModal (modalName) {
+        this.setState({currentModal: modalName});
+    }
+    closeModal () {
+        this.setState({currentModal: null});
+    }
     render () {
         let {
+            backdropLibraryProps,
             basePath,
             blocksProps,
+            costumeLibraryProps,
             greenFlagProps,
             projectData, // eslint-disable-line no-unused-vars
+            spriteLibraryProps,
             spriteSelectorProps,
             stageProps,
             stopAllProps,
@@ -47,6 +65,26 @@ class GUI extends React.Component {
                 media: basePath + 'static/blocks-media/'
             }
         });
+        spriteSelectorProps = defaultsDeep({}, spriteSelectorProps, {
+            openNewBackdrop: () => this.openModal('backdrop-library'),
+            openNewCostume: () => this.openModal('costume-library'),
+            openNewSprite: () => this.openModal('sprite-library')
+        });
+        spriteLibraryProps = defaultsDeep({}, spriteLibraryProps, {
+            mediaLibrary: this.mediaLibrary,
+            onRequestClose: this.closeModal,
+            visible: this.state.currentModal == 'sprite-library'
+        });
+        costumeLibraryProps = defaultsDeep({}, costumeLibraryProps, {
+            mediaLibrary: this.mediaLibrary,
+            onRequestClose: this.closeModal,
+            visible: this.state.currentModal == 'costume-library'
+        });
+        backdropLibraryProps = defaultsDeep({}, backdropLibraryProps, {
+            mediaLibrary: this.mediaLibrary,
+            onRequestClose: this.closeModal,
+            visible: this.state.currentModal == 'backdrop-library'
+        });
         if (this.props.children) {
             return (
                 <GUIComponent {... guiProps}>
@@ -61,6 +99,9 @@ class GUI extends React.Component {
                     <Stage vm={vm} {...stageProps} />
                     <SpriteSelector vm={vm} {... spriteSelectorProps} />
                     <Blocks vm={vm} {... blocksProps} />
+                    <SpriteLibrary vm={vm} {...spriteLibraryProps} />
+                    <CostumeLibrary vm={vm} {...costumeLibraryProps} />
+                    <BackdropLibrary vm={vm} {...backdropLibraryProps} />
                 </GUIComponent>
             );
         }
@@ -68,11 +109,14 @@ class GUI extends React.Component {
 }
 
 GUI.propTypes = {
+    backdropLibraryProps: React.PropTypes.object,
     basePath: React.PropTypes.string,
     blocksProps: React.PropTypes.object,
+    costumeLibraryProps: React.PropTypes.object,
     children: React.PropTypes.node,
     greenFlagProps: React.PropTypes.object,
     projectData: React.PropTypes.string,
+    spriteLibraryProps: React.PropTypes.object,
     spriteSelectorProps: React.PropTypes.object,
     stageProps: React.PropTypes.object,
     stopAllProps: React.PropTypes.object,
@@ -80,10 +124,13 @@ GUI.propTypes = {
 };
 
 GUI.defaultProps = {
+    backdropLibraryProps: {},
     basePath: '/',
     blocksProps: {},
+    costumeLibraryProps: {},
     greenFlagProps: {},
     spriteSelectorProps: {},
+    spriteLibraryProps: {},
     stageProps: {},
     stopAllProps: {},
     vm: new VM()
diff --git a/src/containers/sprite-library.js b/src/containers/sprite-library.js
new file mode 100644
index 0000000000000000000000000000000000000000..602fb177609eb9e49351ef9753d929a569ec8ed6
--- /dev/null
+++ b/src/containers/sprite-library.js
@@ -0,0 +1,61 @@
+const bindAll = require('lodash.bindall');
+const React = require('react');
+const VM = require('scratch-vm');
+const MediaLibrary = require('../lib/media-library');
+
+const LibaryComponent = require('../components/library');
+
+class SpriteLibrary extends React.Component {
+    constructor (props) {
+        super(props);
+        bindAll(this, ['setData', 'selectItem', 'setSpriteData']);
+        this.state = {data: [], spriteData: {}};
+    }
+    componentWillReceiveProps (nextProps) {
+        if (nextProps.visible && this.state.data.length === 0) {
+            this.props.mediaLibrary.getMediaLibrary('sprite', this.setData);
+        }
+    }
+    setData (data) {
+        this.setState({data: data});
+        for (let sprite of data) {
+            this.props.mediaLibrary.getSprite(sprite.md5, this.setSpriteData);
+        }
+    }
+    setSpriteData (md5, data) {
+        let spriteData = this.state.spriteData;
+        spriteData[md5] = data;
+        this.setState({spriteData: spriteData});
+    }
+    selectItem (item) {
+        var spriteData = JSON.stringify(this.state.spriteData[item.json]);
+        this.props.vm.addSprite2(spriteData);
+    }
+    render () {
+        let libraryData = Object.keys(this.state.spriteData).map((libraryKey) => {
+            let libraryItem = this.state.spriteData[libraryKey];
+            return {
+                name: libraryItem.objName,
+                md5: libraryItem.costumes[0].baseLayerMD5,
+                json: libraryKey
+            };
+        });
+        return <LibaryComponent
+            title="Sprite Library"
+            visible={this.props.visible}
+            data={libraryData}
+            mediaLibrary={this.props.mediaLibrary}
+            onRequestClose={this.props.onRequestClose}
+            onItemSelected={this.selectItem}
+        />;
+    }
+}
+
+SpriteLibrary.propTypes = {
+    vm: React.PropTypes.instanceOf(VM).isRequired,
+    mediaLibrary: React.PropTypes.instanceOf(MediaLibrary),
+    visible: React.PropTypes.bool,
+    onRequestClose: React.PropTypes.func
+};
+
+module.exports = SpriteLibrary;
diff --git a/src/containers/sprite-selector.js b/src/containers/sprite-selector.js
index 2f5c83d44f702f089f5f7019c68772dbc5fbd0f4..c30ac713784551c4fc8b6bf7f8495e99354bea2a 100644
--- a/src/containers/sprite-selector.js
+++ b/src/containers/sprite-selector.js
@@ -25,12 +25,18 @@ class SpriteSelector extends React.Component {
     render () {
         const {
             vm, // eslint-disable-line no-unused-vars
+            openNewSprite,
+            openNewCostume,
+            openNewBackdrop,
             ...props
         } = this.props;
         return (
             <SpriteSelectorComponent
                 value={this.state.targets.editingTarget && [this.state.targets.editingTarget]}
                 onChange={this.onChange}
+                openNewSprite={openNewSprite}
+                openNewCostume={openNewCostume}
+                openNewBackdrop={openNewBackdrop}
                 sprites={this.state.targets.targetList.map(target => (
                     {
                         id: target[0],
@@ -44,7 +50,10 @@ class SpriteSelector extends React.Component {
 }
 
 SpriteSelector.propTypes = {
-    vm: React.PropTypes.object.isRequired
+    vm: React.PropTypes.object.isRequired,
+    openNewSprite: React.PropTypes.func,
+    openNewCostume: React.PropTypes.func,
+    openNewBackdrop: React.PropTypes.func
 };
 
 module.exports = SpriteSelector;
diff --git a/src/lib/media-library.js b/src/lib/media-library.js
new file mode 100644
index 0000000000000000000000000000000000000000..6f33b25c919a99d98abdafebc1a24a18f3eb0ce1
--- /dev/null
+++ b/src/lib/media-library.js
@@ -0,0 +1,80 @@
+const xhr = require('xhr');
+
+const LIBRARY_PREFIX = 'https://cdn.scratch.mit.edu/scratchr2/static/' +
+    '__8d9c95eb5aa1272a311775ca32568417__/medialibraries/';
+const LIBRARY_URL = {
+    sprite: LIBRARY_PREFIX + 'spriteLibrary.json',
+    costume: LIBRARY_PREFIX + 'costumeLibrary.json',
+    backdrop: LIBRARY_PREFIX + 'backdropLibrary.json'
+};
+const SPRITE_OBJECT_PREFIX = 'https://cdn.assets.scratch.mit.edu/internalapi/asset/';
+const SPRITE_OBJECT_SUFFIX = '/get/';
+
+class MediaLibrary {
+    constructor () {
+        /*
+         * Cached library data, from JSON.
+         * @type {Object}
+         */
+        this._libraryData = {};
+
+        /**
+         * Cached sprite data, from JSON.
+         * @type {Object.<!string, Object>}
+         */
+        this._spriteData = {};
+    }
+
+    /**
+     * Get the media library data for a particular scratchr2 library.
+     * In the future, load this from `scratch-storage` asset manager,
+     * e.g., for offline support.
+     * @param {string} libraryType Type of library, i.e., sprite, costume, sound, backdrop.
+     * @param {!Function} callback Callback, called with list of data.
+     */
+    getMediaLibrary (libraryType, callback) {
+        if (!this._libraryData.hasOwnProperty(libraryType)) {
+            this._libraryData[libraryType] = null;
+        }
+        if (this._libraryData[libraryType]) {
+            callback(this._libraryData[libraryType]);
+        } else {
+            xhr.get({
+                useXDR: true,
+                url: LIBRARY_URL[libraryType]
+            }, (err, response, body) => {
+                if (!err) {
+                    let data = JSON.parse(body);
+                    this._libraryData[libraryType] = data;
+                    callback(this._libraryData[libraryType]);
+                }
+            });
+        }
+    }
+
+    /**
+     * Get media library info for a specific scratchr2 sprite.
+     * In the future, load this from `scratch-storage` asset manager,
+     * e.g., for offline support.
+     * @param {string} url URL to sprite (md5.json).
+     * @param {!Function} callback Callback, called with sprite data.
+     */
+    getSprite (url, callback) {
+        if (this._spriteData.hasOwnProperty(url)) {
+            callback(url, this._spriteData[url]);
+        } else {
+            xhr.get({
+                useXDR: true,
+                url: SPRITE_OBJECT_PREFIX + url + SPRITE_OBJECT_SUFFIX
+            }, (err, response, body) => {
+                if (!err) {
+                    let data = JSON.parse(body);
+                    this._spriteData[url] = data;
+                    callback(url, data);
+                }
+            });
+        }
+    }
+}
+
+module.exports = MediaLibrary;