diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index 8e041b1aa6..19c78f1385 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -47,8 +47,4 @@
// include all of our vendored js
//= require_tree ../../../vendor/assets/javascripts/.
-// Require VideoJS and VideoJS quality selector for embedded player
-//= require video.js/dist/video.min.js
-//= require @silvermine/videojs-quality-selector/dist/js/silvermine-videojs-quality-selector.min.js
-
//= require_tree .
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index 30153ba8ee..f4af972a20 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -55,6 +55,3 @@
@import 'avalon';
@import "datatables";
-
-@import "video.js/dist/video-js.css";
-@import "@silvermine/videojs-quality-selector/dist/css/quality-selector.css";
diff --git a/app/assets/stylesheets/embed.scss b/app/assets/stylesheets/embed.scss
deleted file mode 100644
index 1dfb32109b..0000000000
--- a/app/assets/stylesheets/embed.scss
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright 2011-2024, The Trustees of Indiana University and Northwestern
- * University. Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- *
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed
- * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
- * CONDITIONS OF ANY KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations under the License.
- * --- END LICENSE_HEADER BLOCK ---
-*/
-
-.video-container {
- position: absolute;
-}
-
-.video-js {
- position: relative;
- z-index: 0;
-}
-
-.video-title {
- position: absolute;
- z-index: 1;
- top: 5px;
- left: 8px;
-}
-
-.video-title .video-link {
- color: white;
- font-size:150%;
-}
-
-.video-js.vjs-playing + .video-title {
- display: none;
-}
-
-// @silvermine/videojs-quality-selector
-.vjs-menu {
- li {
- font-size: 1em;
- }
-}
-
-.video-js .vjs-control-bar {
- /* Audio: Make the controlbar visible by default */
- display: -webkit-box;
- display: -webkit-flex;
- display: -ms-flexbox;
- display: flex;
- /* Increase the control-bar icons/text size */
- font-size: 120%;
-}
-
-/* Make player height minimum to the controls height so when we hide
-video/poster area the controls are displayed correctly. */
-.video-js.vjs-audio {
- min-height: 3.7em;
-}
-
-.video-js .vjs-progress-control:hover .vjs-play-progress:after {
- display: none;
-}
-
-/* Show poster image when playback ends */
-.video-js.vjs-ended .vjs-poster {
- display: block;
-}
-
-.video-js .vjs-current-time {
- display: block;
-}
-
-/* Put playhead on top of markers */
-.video-js .vjs-play-progress:before {
- z-index: 101;
-}
-
-/* time-control elements */
-.video-js .vjs-time-control {
- min-width: 0.5rem;
- padding: 0 0.25rem;
-}
-
-.vjs-time-divider {
- display: block;
-}
-
-.vjs-duration {
- display: block !important;
-}
-
-/* big-play button */
-.video-js .vjs-big-play-button {
- margin-left: auto;
- margin-right: auto;
- height: 2.5rem;
- width: 2.5rem;
- line-height: 2.5rem;
- border-radius: 50%;
- scale: 2;
- border: 0.1rem solid #fff;
-}
-
-/* captions button selection */
-.captions-on {
- border-bottom: 0.45rem ridge;
-}
-
-.vjs-custom-external-link .vjs-icon-placeholder {
- font-size: 120%;
-}
diff --git a/app/controllers/master_files_controller.rb b/app/controllers/master_files_controller.rb
index 31f2011ffe..c1aa6c40b0 100644
--- a/app/controllers/master_files_controller.rb
+++ b/app/controllers/master_files_controller.rb
@@ -71,14 +71,6 @@ def show
end
def embed
- if can? :read, @master_file
- @stream_info = secure_streams(@master_file.stream_details, @master_file.media_object_id)
- @stream_info['t'] = view_context.parse_media_fragment(params[:t]) # add MediaFragment from params
- @stream_info['link_back_url'] = view_context.share_link_for(@master_file)
- end
-
- @player_width = "100%"
- @player_height = "100%"
respond_to do |format|
format.html do
response.headers.delete "X-Frame-Options"
diff --git a/app/javascript/components/MediaObjectRamp.jsx b/app/javascript/components/MediaObjectRamp.jsx
index 8dd48fecb9..047ae20f8b 100644
--- a/app/javascript/components/MediaObjectRamp.jsx
+++ b/app/javascript/components/MediaObjectRamp.jsx
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2023, The Trustees of Indiana University and Northwestern
+ * Copyright 2011-2024, The Trustees of Indiana University and Northwestern
* University. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*
diff --git a/app/javascript/components/PlaylistRamp.jsx b/app/javascript/components/PlaylistRamp.jsx
index 78fe26ebe6..7bbd1a2e09 100644
--- a/app/javascript/components/PlaylistRamp.jsx
+++ b/app/javascript/components/PlaylistRamp.jsx
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2023, The Trustees of Indiana University and Northwestern
+ * Copyright 2011-2024, The Trustees of Indiana University and Northwestern
* University. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*
diff --git a/app/javascript/components/embeds/EmbeddedRamp.jsx b/app/javascript/components/embeds/EmbeddedRamp.jsx
new file mode 100644
index 0000000000..7846a0d998
--- /dev/null
+++ b/app/javascript/components/embeds/EmbeddedRamp.jsx
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2011-2024, The Trustees of Indiana University and Northwestern
+ * University. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed
+ * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ * --- END LICENSE_HEADER BLOCK ---
+*/
+
+import React from 'react';
+import {
+ IIIFPlayer,
+ MediaPlayer
+} from '@samvera/ramp';
+import 'video.js/dist/video-js.css';
+import "@samvera/ramp/dist/ramp.css";
+import './Ramp.scss';
+
+const Ramp = ({
+ urls,
+ media_object_id
+}) => {
+ const [manifestUrl, setManifestUrl] = React.useState('');
+ const [startCanvasId, setStartCanvasId] = React.useState();
+ const [startCanvasTime, setStartCanvasTime] = React.useState();
+ let interval;
+
+ React.useEffect(() => {
+ const { base_url, fullpath_url } = urls;
+ // Split the current path from the time fragment in the format .../:id?t=time
+ let [fullpath, start_time] = fullpath_url.split('?t=');
+ // Split the current path in the format /master_files/:mf_id/embed
+ let [_, __, mf_id, ___] = fullpath.split('/');
+ // Build the manifest URL
+ let url = `${base_url}/media_objects/${media_object_id}/manifest.json`;
+
+ // Set start Canvas ID and start time in the state for Ramp
+ setStartCanvasId(
+ mf_id && mf_id != undefined
+ ? `${base_url}/media_objects/${media_object_id}/manifest/canvas/${mf_id}`
+ : undefined
+ );
+ setStartCanvasTime(
+ start_time && start_time != undefined
+ ? parseFloat(start_time)
+ : undefined
+ );
+ setManifestUrl(url);
+
+ interval = setInterval(addControls, 500);
+
+ // Clear interval upon component unmounting
+ return () => clearInterval(interval);
+ }, []);
+
+ const addControls = () => {
+ let player = document.getElementById('iiif-media-player');
+ if (player && player.player) {
+ let embeddedPlayer = player.player
+
+ // Player API handling
+ window.addEventListener('message', function(event) {
+ var command = event.data.command;
+
+ if (command=='play') embeddedPlayer.play();
+ else if (command=='pause') embeddedPlayer.pause();
+ else if (command=='toggle_loop') {
+ embeddedPlayer.loop() ? embeddedPlayer.loop(false): embeddedPlayer.loop(true);
+ embeddedPlayer.autoplay() ? embeddedPlayer.autoplay(false) : embeddedPlayer.autoplay(true);
+ }
+ else if (command=='set_offset') embeddedPlayer.currentTime(event.data.offset); // time is in seconds
+ else if (command=='get_offset') event.source.postMessage({'command': 'currentTime','currentTime': embeddedPlayer.currentTime()}, event.origin);
+ });
+
+ /*
+ Quality selector extends outside iframe for audio items, so we need to disable that control
+ and rely on the quality automatically selected by the user's system.
+ */
+ if (embeddedPlayer.isAudio()) { embeddedPlayer.controlBar.removeChild('qualitySelector'); }
+
+ // Create button component for "View in Repository" and add to control bar
+ let repositoryUrl = Object.values(urls).join('/').replace('/embed', '');
+ let position = embeddedPlayer.isAudio() ? embeddedPlayer.controlBar.children_.length : embeddedPlayer.controlBar.children_.length - 1;
+ var viewInRepoButton = embeddedPlayer.getChild('ControlBar').addChild('button', {
+ clickHandler: function(event) {
+ window.open(repositoryUrl, '_blank').focus();
+ }
+ }, position);
+
+ viewInRepoButton.addClass('vjs-custom-external-link');
+ viewInRepoButton.el_.setAttribute('style', 'cursor: pointer;');
+ viewInRepoButton.controlText('View in Repository');
+
+ // Add button icon
+ document.querySelector('.vjs-custom-external-link .vjs-icon-placeholder').innerHTML = ''
+
+ // This function only needs to run once, so we clear the interval here
+ clearInterval(interval);
+ }
+ };
+
+ return (
+