Open In App

Chrome Extension – Youtube Bookmarker

Last Updated : 28 Mar, 2021
Improve
Improve
Like Article
Like
Save
Share
Report

In this article, we’ll develop a chrome extension using which the user can create bookmarks corresponding to different timestamps, store them somewhere(for now in chrome local storage), and retrieve the bookmarks(stored as timestamped youtube video links).

The code is hosted here: GitHub. The video explainer for the main extension can be found here(in the main extension’s repo). Note that the project is still a Work in Progress at the time of writing this article.

Let’s get started. Broadly speaking, we’ll divide this article into 3 parts. In the first part, we’ll hook up the extension so that we can access our in-development extension in Chrome. In the second part, we’ll have a look at a basic extension and talk about the code and the architecture. In the third part, we’ll develop the main extension.

1. Testing our extension during development

To test and debug our extension during the development phase, we’ll follow the below steps to get a basic extension up and running.

  1. Create the manifest.json file and the project structure as shown above or download them from the link at the top of the article.
  2. Go to chrome://extensions from your browser. <ss here>
  3. Toggle the ‘Developer Mode’ to ON.
  4. Click on the ‘Load Unpacked’ option to load the Extension folder for test, debug and further development.
  5. At this point, you should see your Extension on the right side of the address bar of the chrome.

Browser screen snippet to show various options used to set up a functioning extension

Now that we have loaded our Extension folder on chrome, we are ready to build a basic chrome extension and test it out.

2. Basic Chrome Extension – Hi, there!

We’ll build an extension that will say, “Hi, there!” when the user clicks on the Extension icon. The code can be found here.

Clicking the ‘H’ icon opens the popup.html with a Hi, there! message

For that, we’ll need the following files.

  • manifest.json – To hold information about the Chrome extension.
  • popup.html – To say, “Hi, there!” when the user clicks on the extension icon.
  • popup.js – No significant work of this file at this point. Although, we’ll keep it here.
  • background.js – To load up the extension with the basic backend functionality. Not required for the Hi, there! Extension.
  • jQuery.js – We’ll include jQuery to help with development.

1. manifest.json file

{
"name": "Hi, there! Extension",
"version": "1.0",
"description": "I greet",
"permissions": ["activeTab"],
"options_page": "options.html",
"background": {
"scripts": ["background.js"],
"persistent": false
},
"browser_action": {
"default_popup": "popup.html",
"default_title": "Hi, there Extension"
},
"manifest_version": 2
}

Let’s look at the key-value pairs in the manifest.json file in more detail.

  1. name – This is the name of the extension. In the browser screen snapshot, you can see this as ‘Hi, there! Extension.
  2. version – This is the version of the extension. It’s taken as 1.0 because when we upload it for review to the chrome web store, it would be 1.0 initially. In the development phase, you may name it as 0.0, then 0.1, and so on as well.
  3. description – Description of the extension. It’s a good practice to keep it concise.
  4. permissions – The permissions key-value pair holds the different permission that the Extension requires access to work properly. The value is an array of strings. The elements of the array can be either known strings, or a pattern match(usually used to match web addresses). For example, https://youtube.com/* -> gives permission to the extension for all links with domain name youtube.com. Here, the ‘*’ is a wildcard pattern. Also, note that some of the permissions declared here may need the user’s explicit approval as well. You can read more about permissions in the official docs.
  5. options_page – The options page is used to give a user more options. The user can access the options page by either right-clicking on the extension and click on the ‘options’ button from the menu, or going to the options page from the ‘chrome://extensions’ page. The setting used in the above manifest will cause the options.html page to open in a new tab. It is also possible to open the ‘options’ page in the same tab in an embedded manner. In the basic ‘Hi, there!’ extension, we don’t need this option. You can read more about the ‘options’ in the official docs. perhaps add more here
  6. background – background script(s) is declared here. The background script is used to listen to events and react to them, as the extension is an event-driven paradigm. A background page is loaded when it is needed and unloaded when it goes idle, i.e., the background script will keep running while it is performing an action and unload after the action is performed. In our ‘Hi, there!’ extension, we haven’t used the background script functionality. You can also notice that the “persistent” key is set to false because it’s the standard practice, and as mentioned in the docs, the only time we should set “persistent” to true is when the extension is using the chrome.webRequest API to block or modify network requests, which we’re not doing.
  7. browser_action – The browser_action is used by the Extension to put the icon on the right side of the chrome’s address bar. The extension then listens for clicks, and when the icon is clicked, it does something. In our case, it opens the popup.html page containing the Hi, there! greeting. The ‘default_title’ field value is a string that is displayed when the user hovers over the extension’s icon.
  8. manifest_version – At the time of writing this article, developers are being encouraged to try out the soon-to-be-launched manifest_version 3. However, version 2 is still available and familiar, so we’ll use version 2. For version 3, you can start here.

 

2. popup.html file

HTML




<!DOCTYPE html>
<html>
  <head></head>
  <body>
    <h5>Hi, there!</h5>
    <script src="jquery-3.5.1.js">
    </script><script src="popup.js">
    </script>
  </body>
</html>


How does the Basic Chrome Extension work?

After we’ve ‘load unpacked’ the chrome extension, the manifest.json file is processed. After this, the ‘background’ is run to initialize the extension and for listening to other events. Now, that we’ve got an understanding of chrome extension from the development point of view, we’ll look at the main Extension that we want to build – YouTube Bookmarker Extension.

Main Extension – YouTube Bookmarker

The video demo for the YouTube Bookmarker extension can be found here.
 

Features of the Extension:

  • Works on youtube.com tabs in a Chrome extension to bookmarks points in videos.
  • When the user clicks on the <input> tag in the popup.html window, the extension takes note of the timestamp at which the <input> tag was ‘on focus’. After taking the input from <input> tag as well, it appends it in the ‘bookmarked points’ section of popup.html, as seen in the explainer video.

 

Project Directory structure:
 

project directory structure

 

Source Code:

1. As the extension would access and store the web addresses, i.e., youtube video addresses, it becomes imperative to have a look at the different kinds of timestamps that we can access from the DOM.

If one runs

var result1 = document.querySelector('#movie_player >
div.ytp-chrome-bottom > div.ytp-progress-bar-container >
div.ytp-progress-bar').getAttribute("aria-valuetext");

It will give the timestamp in word format, as defined below. You are being encouraged to run the above query selector in the chrome’s window console accessible by pressing ‘Ctrl+Shift+J’.

The below are the types of timestamps that can be retrieved in word format by running the query selector above.

1 Hours 48 Minutes 31 Seconds of 2 Hours 56 Minutes 33 Seconds
//for video greater than 1 hours long and the current
//timestamp being greater than 1 hour too.


-> 0 Minutes 46 Seconds of 2 Hours 56 Minutes 33 Seconds
//for video greater than 1 hours long and
//current timestamp < 1 minute

-> 8 Minutes 33 Seconds of 2 Hours 56 Minutes 33 Seconds
//for video greater than 1 hours long and
//current timestamp >= 1 minute and less than 1 hour

-> 0 Minutes 0 Seconds of 0 Minutes 56 Seconds
//for video less than 1 minute long and
//current timestamp < 1 minute

2. manifest.json:

{
"name": "Youtube video bookmarker",
"version": "1.0",
"description": "An extension that bookmarks time points in youtube video",
"permissions": ["activeTab", "declarativeContent",
         "storage", "tabs", "https://www.youtube.com/*"],
"options_page": "options.html",
"background": {
"scripts": ["background.js"],
"persistent": false
},
"externally_connectable": {
"matches": ["*://*.youtube.com/*"]
},
"browser_action": {
"default_popup": "popup.html",
"default_title": "YouTube Video Bookmarker - GFG"
},
"manifest_version": 2
}

The fields of manifest.json file are defined below:

  • name, version, description, browser_action. manifest_version, options_page, – Already covered in adequate detail in the previous look at manifest.json
  • permissions – An array of strings.
    • activeTab – Have access to the active tab
    • storage – Have access to storage APIs of chrome.
    • https://youtube.com/* – pattern-based hostname to which the access if required for manipulation and working of extension
    • tabs – Have access to ‘tabs’ API.
    • declarativeContent – To take actions depending on the content of the current page.

3. popup.html:

HTML




<!DOCTYPE html>
<html>
  <head></head>
  <body>
    <div>
      <div id="bookmarktakerdiv">
        <h5 style="color:darkgreen">Youtube video Bookmarker</h5>
        <span id="currts">xx:xx</span>
        <span id="receiptts" style="color:darkgreen"></span>
        <input type="text" id="bookmarkdesc" />
        <button id="submitbookmark" type="button">Bookmark</button>
      </div>
      <hr /><div id="bookmarklistdiv">
        
<p>Bookmarked points</p>
  
      <ul id="bookmark_ulist">
          
 <!-- <li><span id="ts">xx:xx</span>
<span id="note">example desc</span></li>
<li><span id="ts">xx:xx</span>
<span id="note">example desc</span>
</li><li><span id="ts">xx:xx</span>
<span id="note">example desc</span></li> -->
      </ul></div>
    </div>
    <script src="jquery-3.5.1.js"></script>
    <script src="popup.js"></script>
      
    <!-- <script src="options.js" /> -->
  </body></html>


Here we have the implementation of the popup.html file:

  • We have 2 <div> tag.
    • #bookmarktakerdiv – Takes bookmark and timestamp as input from the user.
    • #bookmarklistdiv – Holds an unordered list of bookmarks taken already for the current video. The ‘current video’ here refers to the video currently being played on the tab currently active.
  • We have 1 <ul> ( #bookmark_ulist) which holds the hyperlinked bookmarks and their timestamp.

 

4. popup.js:

Javascript




'use strict';
  
$(function() {
  
        //retrieve data from local for already stored notes
        // chrome.runtime.sendMessage({ method: "getbookmarks" })
          //todo, not connected to background.js
  
        // $('#bookmark_ulist > li > span > a').on("click", function() {
        // console.log('li clicked event fired');
    }) //todo
  
//todo
//make same page reload of youtube video to bookmarked point
  
$('#bookmarkdesc').focus(function() {
  
        console.log('focus bookmark description input field') //executing
  
        //for sending a message
        chrome.runtime.sendMessage({ method: "gettimestampforcurrentpoint" });
  
        //for listening any message which comes from runtime
        chrome.runtime.onMessage.addListener(tsvalue);
  
  
  
        var ts, tslink;
  
        function tsvalue(msg) {
            // Do your work here
            if (msg.method == "tsfind") {
                ts = msg.tsvaltopopup;
                tslink = msg.fl;
  
                // console.log('ts tslink' + ts + ' ' + tslink) 
                $('#submitbookmark').on('click', function() {
                    // console.log('submitnote button clicked')
                    //#bookmark_ulist
  
                    var bookmarkinput = $('#bookmarkdesc').val();
  
                    // console.log('#bookmarkinput val ' + bookmarkinput); 
                    $('#bookmark_ulist').append('<li><span>' + ts +
                                                ' - <a href="' + tslink + '">' +
                                                bookmarkinput + 
                                                '</a></span></li>');
                    console.log('list item appended to bookmark_ulist')
  
                // chrome.storage.local.set({ "bklocal": bookmarkinput,
                // "tslocal": ts, "vidlinklocal": tslink })
  
                //popup > bg > fg while setting
                //while getting, see..
                // chrome.runtime.sendMessage({ method: "setlocalstorage",
                // bookmarkvalue: bookmarkinput, timestamp: ts, vidlink: tslink})
  
  
                });
  
                $('#currts').text(msg.tsvaltopopup)
                $('#receiptts').text('got timestamp')
  
  
                // makeentryinstorage(bookmarkinput, ts, tslink);
  
            }
        }
  
  
    })
    // });
  
  
  
// chrome.storage.local.set({ note: inputnote,
//timestamp: time, videolink: link });
  
// function makeentryinstorage(bookmarkinput, time, link) {
//chrome.runtime.sendMessage({ method: "storeinlocal", note: bookmarkinput,
// timestamp: time, videolink: link });
  
// } //todo
  
// makeentryinstorage(bookmarkinput, ts, tslink);
  
  
// $('#pointsli').append('<li><span><a href="' + $(msg.fl) + '">' +
// noteinput + '</a></span></li>');
  
  
// console.log('msg obj popup.js ' + msg);
// console.log('popupjs noteinput ' + noteinput);
// $('#pointsli').append('<li><span><a href="' + $(msg.fl) + '">' + 
// noteinput + '</a></span></li>');
  
// { method: "tsfind", tsvaltopopup: msg.tsval, fl: msg.finallink }


The below list explains the approach we took for popup.js:

  • When the user clicks on the <input> tag uniquely identified by id ‘bookmarkdesc‘, the event is fired with { method: “gettimestampforcurrentpoint” } object. This event is captured by background.js as we’ll see later.
  • Function tsvalue() is fired from the background.js to transfer video information from the browser window frame to the extension window frame.
  • Inside the tsvalue() function, the click event on ‘#submitbookmark‘ button appends the <li> element containing the timestamp and the bookmark hyperlinked by timestamped YouTube video link to the #bookmark_ulist in popup.html.
  • So, to explain again, after the user clicks on the extension icon, if the video is a YouTube video in the main browser window frame, the timestamp and bookmark are built into an <li> element which is appended to the <ul> in popup.html.
  • As you can note, popup.js adds interactivity to popup.html and communicated with background.js using an event-driven paradigm.

 

5. background.js

Javascript




"use strict";
  
// to check connection of fg with bg
  
chrome.runtime.onMessage.addListener(checktimestamp);
  
function checktimestamp(msg) {
  // Do your work here
  if (msg.method == "gettimestampforcurrentpoint") {
    console.log("bg.js gettimestampforcurrrentpoint called");
    chrome.tabs.executeScript(null, { file: "./gettimestamp.js" }, () => {
      console.log("injected gettimestamp.js file into YT window DOM.");
      // gettimestamp.js will execute now in main chrome window which
      // is running youtube.com/somevideo
    });
  }
}
  
// first this runs
chrome.tabs.onActivated.addListener((tab) => {
  console.log(tab);
  chrome.tabs.get(tab.tabId, (c) => {
    // console.log(c.url);
    if (/^https:\/\/www\.youtube/.test(c.url)) {
      // above pattern tests for the youtube hostname.
      // If youtube is running in the active tab,
      // it injects ./foreground.js in DOM.
  
      chrome.tabs.executeScript(null, { file: "./foreground.js" }, () => {
        console.log("i injected fg using bg script in youtube webpages");
      });
    }
  });
  
  // fetch data from local storage
  // var windowlink;
  // chrome.tabs.get(tab.tabId, a => {
  //     windowlink = a.url;
  // });
  // console.log('testretrieval bg.js')
  // chrome.runtime.sendMessage({ method: "testretrieval" });
});
  
// chrome.browserAction.onClicked.addListener(function(tab) {
//     console.log('browser action called' + tab);
//     //     // Run the following code when the popup is opened
// });
  
//for sending a message
// chrome.runtime.sendMessage({greeting: "hello"}, function(response) {
  
// });
  
// chrome.runtime.onMessage.addListener(retrievenotes)
  
// function retrievenotes(msg) {
//     if (msg.method == "getnotes") {
//         //todo
//     }
  
// }
  
// chrome.runtime.onMessage.addListener(storelocal); //todo
  
// function storelocal(msg) { //todo
//     if (msg.method == "storeinlocal") {
//         chrome.storage.local.set({ note: inputnote, timestamp: time,
//          videolink: link });
//     }
// }
  
chrome.runtime.onMessage.addListener(getcurrenttimestamp);
  
function getcurrenttimestamp(msg) {
  if (msg.method == "sendtimestamptobg") {
    var temp1 = msg.tsvalue;
    var temp2 = msg.finallink;
    console.log("msg.tsvalue value: " + msg.tsval);
    console.log("msg.finallink " + msg.finallink);
    //tsval and finallink being received properly in the bg consolelog
  
    chrome.runtime.sendMessage({
      method: "tsfind",
      tsvaltopopup: temp1,
      fl: temp2,
    });
    // , function() {
    //     console.log('tsval to popup');
    // })
  }
}
  
chrome.runtime.onMessage.addListener(localstorageset);
  
function localstorageset(msg) {
  if (msg.method == "setlocalstorage") {
    console.log("setlocalstorage background.js"); //called
    // chrome.runtime.sendMessage({ method: "setlocalstorage",
    // bookmarkvalue: bookmarkinput, timestamp: ts, vidlink: tslink })
    // chrome.storage.local.set({ "bklocal": msg.bookmarkvalue,
    // "tslocal": msg.timestamp, "vidlinklocal": msg.vidlink })
    // chrome.storage.local.set({ "password": "123" })
    // chrome.runtime.sendMessage({ method: "localstoragesetrequest",
    // pass: "hellopass" });
  }
}
  
// chrome.runtime.onInstalled.addListener(function() {
//   chrome.storage.sync.set({color: '#3aa757'}, function() {
//     console.log('The color is green.');
//   });
//   chrome.declarativeContent.onPageChanged.removeRules(undefined, function() {
//     chrome.declarativeContent.onPageChanged.addRules([{
//       conditions: [new chrome.declarativeContent.PageStateMatcher({
//         pageUrl: {hostEquals: 'developer.chrome.com'},
//       })],
//       actions: [new chrome.declarativeContent.ShowPageAction()]
//     }]);
//   });
// });


Let’s check out what we did in the background.js file:

  • When the user clicks on the <input> field to make a bookmark, in the background script, checktimestamp() function is called which injects the gettimestamp.js script into the window of the browser. As the file name suggests, it will give us the current timestamp and the timestamped YouTube video link, which we’ll listen to using an eventListener getcurrenttimestamp.
  • The function getcurrenttimestamp will send the timestamp(in seconds) and the timestamped video link to popup.js(the extension’s context/frame) after receiving it from an event which is fired with an object property {msg.method = ‘sendtimestamptobg‘}. This event listens to a message sent from gettimestamp.js script.

 

6. gettimestamp.js

Javascript




var result1 = document
  .querySelector(
    "#movie_player > div.ytp-chrome-bottom >div.ytp-progress-bar-container >div.ytp-progress-bar"
  )
  .getAttribute("aria-valuetext");
//example of result1 is 1 Hours 48 Minutes 31 Seconds of 2 Hours 56 Minutes 33 Seconds.
// Here, the timestamp is 01:48:31 in hh:mm:ss format out of a 02:56:33 long video.
  
// construct link to exact point here
var temparr = result1.split(" ");
  
var tshhmmss_string;
  
if (temparr[6] == "Hours") {
  tshhmmss_string = +"00:" + temparr[0] + ":" + temparr[2];
} else if (temparr[1] == "Hours") {
  tshhmmss_string = temparr[0] + ":" + temparr[2] + ":" + temparr[4];
} else if (temparr[6] == "Minutes") {
  tshhmmss_string = "00:" + temparr[0] + ":" + temparr[2];
}
  
console.log("gettimestamp.js " + result1);
  
var windowlink = window.location.href;
// can be stored in pagelink.
  
console.log("gettimestamp.js windowlink " + windowlink);
  
// find index of v= substring
var idx = windowlink.indexOf("v=");
// return index from where the 'v=' substring starts in the windowlink.
// For example, in https://www.youtube.com/watch?v=JaIU4CteN50, idx = 30.
// indexOf function returns -1 if the substring is not found in the string.
  
console.log("gettimestamp.js idx value " + idx);
  
// console.log('tres gettimestamp.js ' + tres); fine - format hh:mm:ss
  
// console.log('typeof tres gettimestamp.js ' + typeof(tres));
//working fine - returns string
  
function getseconds(timestamphhmmss) {
  var x = timestamphhmmss.split(":");
  var seconds = parseInt(x[0]) * 60 * 60 + parseInt(x[1]) * 60 + parseInt(x[2]);
  console.log("seconds calculated gettimestamp.js getinseconds" + seconds);
  return seconds;
} //function working fine
  
var timeinseconds = getseconds(tshhmmss_string); //working fine - returns number
  
// console.log('tsinsec gettimestamp.js ' + tsinsec); fine
  
var windowlinkfinal;
  
if (idx == -1) {
  windowlink = "https://youtube.com";
  //in case of substring not found, i.e. bad case, store youtube.com as default.
} else {
  windowlinkfinal =
    windowlink.substr(idx + 2, 11) +
    "&t=" +
    timeinseconds;
  console.log("gettimestamp.js windowlinkfinal " + windowlinkfinal);
} // pagelinkfinal fine
  
chrome.runtime.sendMessage({
  method: "sendtimestamptobg",
  tsvalue: tshhmmss_string,
  finallink: windowlinkfinal,
});


Let’s check out what we did in the gettimestamp.js file above:

  • gettimestamp.js script runs in the browser frame/context. This script uses the querySelector to extract the timestamp in word format, converts it into hh:mm:ss format and in seconds.
  • It also takes the current video link and converts it into a generic format. 

     

The timestamp in hh:mm:ss format and the timestamped video link are emitted for consumption by background.js file. The event is identified by ‘method: “sendtimestamptobg”‘ key.

Further Development Scope for you to try out

Feel free to raise a PR or issue on the repo. The immediate WIP is connecting it to chrome.localStorage.

  • Connect the extension to a local database or Firebase for decoupled data storage.
  • Data structure research and implementation while storing the data.
  • Disconnecting from Chrome’s local storage, and using ‘sync storage’ so that the bookmarks are available on other devices as well
  • Improve the UI/UX of the extension
  • Design the icons and store them in the project directory for custom extension icons

 



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads