SkipAd

QuickSearch

SkipAd is a J1 Template module that lets you watch YouTube videos completely free of advertisements. It wraps the VideoJS player around the YouTube IFrame API so that every pre-roll, mid-roll, and overlay ad is silently blocked — no browser plugin, no paid subscription required.

Beyond simple ad blocking, SkipAd maintains a personal playlist stored inside your own browser, lets you sort and search your watch history, and supports optional loop playback and Picture-in-Picture mode for multitasking.

20-30 Minutes to read

SkipAd is built as a self-contained JavaScript module (UMD) that exports a single global object named skipAd. All user-facing features — video loading, playlist management, search, sort, and I/O — are wired up automatically when the J1 adapter initialises the module.

Key features and benefits:

Ad-free playback

Every YouTube ad format (pre-roll, mid-roll, overlay) is blocked automatically by routing playback through the VideoJS YouTube tech instead of the normal YouTube player. No manual intervention is needed.

Personal playlist

Every video you watch is added to a browser-local playlist stored in localStorage. The list survives page reloads and browser restarts as long as you do not clear your browser data.

Multiple display modes

Your playlist can be displayed as visual Cards (default, showing a thumbnail, title, and metadata at a glance) or as a compact List view suited to longer histories.

Sorting and searching

The playlist can be sorted by watch date, issue date, duration, title, author, category, or type. A full-text search powered by the Lunr.js engine lets you filter entries by any combination of title, author, category, and tags.

Import and export

Your playlist can be exported to a JSON file for backup or sharing and re-imported at any time. A set of hand-picked server playlists is also available for one-click import.

Loop mode

When loop mode is enabled, SkipAd automatically advances to the next video in your playlist when the current one ends — following whichever sort order is currently active.

Picture-in-Picture (PiP)

When your browser supports the Document Picture-in-Picture API, a PiP button appears in the player control bar. Clicking it opens the video in a floating window so you can keep watching while working in other tabs.

Resume playback

SkipAd saves your last playback position for every video. The next time you open the same video it will resume from where you left off instead of starting from the beginning.

How SkipAd Works

Understanding the basic flow helps you get the most out of SkipAd. When you paste a YouTube link and click Watch Video, the following steps happen:

  1. The module extracts the 11-character YouTube video ID from the URL (or accepts a bare ID directly).

  2. A VideoJS player is created inside the #video_container element on the page, configured to use the YouTube tech with ads disabled.

  3. The YouTube IFrame API loads the video without ads and reports playback state back to VideoJS.

  4. Once playback begins, the video is added (or updated) in your local playlist, and its title, author, and duration are captured from the YouTube metadata.

The diagram below shows the relationship between the main components:

  Browser
  ┌───────────────────────────────────────────────────────┐
  │  SkipAd module (skipad.js)                            │
  │                                                       │
  │  ┌─────────────┐   videoLoad    ┌─────────────────┐   │
  │  │ inputWrapper│───────────────►│  embedRunVideo  │   │
  │  │   Handler   │                │  (VideoJS + YT) │   │
  │  └─────────────┘                └────────┬────────┘   │
  │                                          │            │
  │  ┌─────────────┐   add / update          │            │
  │  │  Playlist   │◄────────────────────────┘            │
  │  │   Manager   │                                      │
  │  │(localStorage│   render cards / list                │
  │  │    key:     │──────────────────────────────────►   │
  │  │  playlist)  │                  DOM playlist block  │
  │  └─────────────┘                                      │
  └───────────────────────────────────────────────────────┘

Accepted URL Formats

You can paste any of the following into the video URL input field. SkipAd recognises all standard YouTube link styles as well as bare video IDs.

Format Example

Standard watch URL

https://www.youtube.com/watch?v=dQw4w9WgXcQ

Short URL

https://youtu.be/dQw4w9WgXcQ

Embed URL

https://www.youtube.com/embed/dQw4w9WgXcQ

Old /v/ URL

https://www.youtube.com/v/dQw4w9WgXcQ

Bare 11-character video ID

dQw4w9WgXcQ

If SkipAd cannot find a valid 11-character ID in your input it will silently ignore the request. Double-check that the link is a complete YouTube URL or that the ID is exactly 11 characters long.

Configuration Options

SkipAd is configured through the J1 adapter system. The adapter passes an options object to the module at startup. The most important settings are described below.

videoJS.autoStart

Controls whether the VideoJS player is fully activated once the page loads. When set to false the module still initialises but will not attempt to embed any video until explicitly triggered.

Type Default

boolean

true

videoJS.players.youtube.autoplay

Controls whether a video starts playing automatically as soon as it is loaded into the player. When false the video is loaded and paused, waiting for the user to press play.

Type Default

boolean

true

videoJS.hideControlBar

When set to true, the VideoJS control bar is hidden and the native YouTube controls are suppressed, giving the video a clean, uncluttered look. The bar can be restored at runtime by calling vjsPlayer.removeClass('vjs-youtube-hide-controlbar') from the browser console.

Type Default

boolean

false

videoJS.playbackRates

Configures the playback speed selector shown in the player control bar.

enabled

When true, a speed selector is added to the control bar.

values

An array of numbers that appear in the speed selector. Example: [0.5, 0.75, 1, 1.25, 1.5, 2].

videoJS:
  playbackRates:
    enabled: true
    values:  [0.5, 0.75, 1, 1.25, 1.5, 2]

videoJS.plugins.skipButtons

Adds backward and forward skip buttons to the player control bar so you can jump a fixed number of seconds without dragging the progress bar.

enabled

Activates the skip-buttons plugin.

backward

Number of seconds to skip backward (e.g. 10).

forward

Number of seconds to skip forward (e.g. 30).

surroundPlayButton

When true, the backward button is placed immediately before and the forward button immediately after the main play/pause button.

videoJS:
  plugins:
    skipButtons:
      enabled:           true
      backward:          10
      forward:           30
      surroundPlayButton: true

playlist.loop

Enables sequential auto-play: when a video ends, SkipAd automatically loads the next video in your playlist according to the current sort order.

enabled

Activates loop mode. A toggle switch also appears in the playlist header so the user can enable or disable looping at any time.

pip

When true, and when the browser supports the Document Picture-in-Picture API, a PiP button is added to the control bar. The video will also automatically enter PiP mode when the user switches to another tab while a video is playing.

playlist:
  loop:
    enabled: true
    pip:     true

Module API Reference

The public API of the skipAd module is the skipAd global object. The adapter exposes it after initialisation. The sections below describe every exported component.

skipAd.playlistManager

The playlistManager object is the central data store and rendering engine for the playlist. All data is kept in localStorage under the key playlist.

addEntry(entry)

Adds a new video to the top of the playlist, or — if the same video ID already exists — moves the existing entry to the top and refreshes its watch date. After saving, the visible playlist is re-rendered automatically.

Parameter Type Required Description

entry

Object

yes

A playlist entry object. Required fields: videoId (string), title (string), watchDate (ISO date string). Optional fields: author, duration, category, tags, rating, issueDate, series, episode, type, infoLink, videoLink, lastPosition.

Example:

skipAd.playlistManager.addEntry({
  videoId:   'dQw4w9WgXcQ',
  title:     'Never Gonna Give You Up',
  watchDate: new Date().toISOString()
});

deleteEntry(videoId)

Removes the entry with the given YouTube video ID from the playlist and re-renders the playlist UI.

Parameter Type Required Description

videoId

String

yes

The 11-character YouTube video ID to remove.

clearPlaylist()

Removes all entries from the playlist, invalidates the search index, and re-renders the (now empty) playlist UI. Returns true when entries were removed, false when the playlist was already empty.

This operation cannot be undone unless you have previously exported the playlist to a JSON file.

updateEntryDuration(videoId, durationSeconds)

Updates the stored duration for a playlist entry. This is called automatically once the player reports a valid duration via the durationchange event. You rarely need to call it manually.

Parameter Type Required Description

videoId

String

yes

The 11-character YouTube video ID.

durationSeconds

Number

yes

Video duration in seconds (must be > 0).

updateEntryAuthor(videoId, author)

Updates the channel name (author) for a playlist entry. Called automatically once the YouTube IFrame API supplies the author after playback begins.

Parameter Type Required Description

videoId

String

yes

The 11-character YouTube video ID.

author

String

yes

Channel or author name.

updateEntryPosition(videoId, positionSeconds)

Saves the current playback position so the video can be resumed from the same point next time. Called automatically on pause and end events.

Parameter Type Required Description

videoId

String

yes

The 11-character YouTube video ID.

positionSeconds

Number

yes

Playback position in seconds (must be ≥ 0).

updateWatchDate(videoId)

Refreshes the watch-date timestamp of a playlist entry to the current moment. This keeps the "time ago" display in the playlist accurate after a video is replayed.

Parameter Type Required Description

videoId

String

yes

The 11-character YouTube video ID.

updateEntryRating(videoId, rating)

Saves a star rating (1–5) for a playlist entry. A rating of 0 clears an existing rating.

Parameter Type Required Description

videoId

String

yes

The 11-character YouTube video ID.

rating

Number

yes

Rating value: 1–5, or 0 to clear.

updateEntryFields(videoId, fields)

Updates one or more editable metadata fields for a playlist entry. Only the keys that are present in the fields object are changed; all other fields remain untouched.

Parameter Type Required Description

videoId

String

yes

The 11-character YouTube video ID.

fields

Object

yes

An object containing any combination of: category (string), description (string), episode (number), infoLink (URL string), videoLink (URL string), issueDate (string, converted to ISO format automatically), series (number), tags (string), type (string).

Example:

skipAd.playlistManager.updateEntryFields('dQw4w9WgXcQ', {
  category:  'Music',
  tags:      'pop, 80th, classic',
  rating:    5
});

getEntryPosition(videoId)

Returns the last saved playback position (in seconds) for the given video ID, or 0 if no position has been saved yet.

Parameter Type Required Description

videoId

String

yes

The 11-character YouTube video ID.

Returns: Number — saved position in seconds, or 0.

getNextVideoId(currentVideoId)

Returns the video ID of the next entry in the playlist after the given ID, according to the currently active sort order. Returns null when the current video is the last item or when the playlist has fewer than two entries. Used internally by loop mode.

Parameter Type Required Description

currentVideoId

String

yes

The 11-character YouTube video ID that is currently playing.

Returns: String|null — next video ID, or null if at the end of the list.

sortPlaylist(criterion)

Sorts the in-memory playlist and re-renders the UI using the given sort criterion. The active criterion is persisted so that subsequent renders use the same order.

Parameter Type Required Description

criterion

String

yes

One of: watchDate, watchDateAsc, issueDate, issueDateAsc, duration, durationAsc, title, author, category, type, episode.

searchPlaylist(query)

Runs a full-text search against the playlist using the Lunr.js index. The search covers the title, author, category, and tags fields. Returns the matching entries as an array and re-renders the playlist to show only those results.

Parameter Type Required Description

query

String

yes

Search expression (words, partial words, or phrases).

Returns: Array — matching playlist entry objects.

Example:

const results = skipAd.playlistManager.searchPlaylist('concert live');
console.log(results.length + ' videos found');

clearSearch()

Resets the active search filter and re-renders the full playlist.

buildSearchIndex()

Builds (or rebuilds) the Lunr.js full-text search index from all entries currently in localStorage. The index is then serialised and cached in localStorage under a separate key so it survives page reloads. Called automatically when a playlist is imported; you only need to call this manually if you modify localStorage data outside of the module.

importFromUrl(url)

Fetches a playlist JSON file from the given URL and replaces the current playlist (or merges into it when merge mode is active).

Parameter Type Required Description

url

String

yes

Absolute or root-relative URL pointing to a JSON playlist file.

importFromUrlAsync(url)

Async version of importFromUrl. Returns a Promise that resolves once the import is complete. Used internally by the server playlist import feature.

Parameter Type Required Description

url

String

yes

Absolute or root-relative URL pointing to a JSON playlist file.

importFromFile()

Opens a native file-picker dialog filtered to .json files. After the user selects a file it is read, validated, and imported into localStorage. The playlist is then re-rendered automatically.

exportToFile([filename])

Downloads the current playlist as a JSON file. If no file name is given the file is named skipAd-playlist_yyyy-mm-dd__hh-mm-ss.json using the current date and time.

Parameter Type Required Description

filename

String

no

Custom file name for the download (including .json extension).

Example:

// export with a custom name
skipAd.playlistManager.exportToFile('my-backup.json');
// export with the default timestamped name
skipAd.playlistManager.exportToFile();

renderCurrent()

Re-renders the playlist UI using the currently active display mode (cards or list) and sort order. You rarely need to call this manually — all update*, delete*, import*, and clear* methods call it automatically.

renderCards()

Renders the playlist as a grid of visual cards, each showing the video thumbnail, title, author, watch date, duration, rating, category, and action buttons.

renderPlaylist()

Renders the playlist as a compact list where each row shows a small thumbnail, title, author, and time-ago information.

load()

Reads and returns the full playlist array from localStorage. Returns null when no playlist has been stored yet.

Returns: Array|null

save(playlist)

Serialises the given array to JSON and writes it to localStorage under the key playlist. This is the single point of persistence for all playlist data.

Parameter Type Required Description

playlist

Array

yes

Array of playlist entry objects to persist.

skipAd.playlistIOHandler

Wires up the Import, Export, Clear, and server-playlist Import buttons in the Your playlist section to the corresponding playlistManager methods. Instantiated automatically by the J1 adapter; you do not need to create it yourself.

skipAd.playlistSearchHandler

Connects the playlist search input field to playlistManager.searchPlaylist(). Typing in the field triggers a debounced search after 300 ms; pressing Enter triggers it immediately; pressing Escape clears the search. Instantiated automatically by the J1 adapter.

skipAd.playlistModeSwitchHandler

Manages the Cards toggle switch that appears above the playlist. When the switch is on (default) the playlist renders as cards; when it is off it renders as a list. The selected mode is persisted in localStorage so it survives page reloads.

skipAd.playlistMergeSwitchHandler

Manages the Merge toggle switch. When merge mode is active, importing a playlist (from a file or from the server) adds only videos that do not already exist in your current playlist — duplicates are silently skipped. When merge mode is off (default), importing replaces the current playlist entirely.

skipAd.playlistLoopSwitchHandler

Manages the Loop toggle switch that appears in the playlist header when loop mode is enabled in the configuration. The switch state is persisted in localStorage so the user’s preference survives page reloads.

skipAd.playlistSortHandler

Wires the sort <select> element in the playlist header to playlistManager.sortPlaylist(). Selecting a different sort criterion immediately re-sorts and re-renders the list.

Available sort criteria:

Value Description

watchDate (default)

Most recently watched first.

watchDateAsc

Oldest watched first.

issueDate

Most recently published first.

issueDateAsc

Oldest published first.

duration

Longest video first.

durationAsc

Shortest video first.

title

Alphabetical by title (A → Z).

author

Alphabetical by channel name (A → Z).

category

Alphabetical by category (A → Z).

type

Alphabetical by type (A → Z).

episode

Ascending episode number within each series.

skipAd.inputWrapperHandler

Manages the Video selection section at the top of the page. It handles:

  • The Paste button — reads the clipboard and populates the URL input.

  • Direct paste events (Ctrl+V) inside the input field.

  • The Watch Video button — extracts the video ID and starts playback.

  • The Clear (×) button — empties the URL input field.

  • The Enter key inside the URL input — equivalent to clicking Watch Video.

  • The Escape key inside the URL input — clears the field.

skipAd.inputValueBackgroundHandler

A visual helper that changes the background colour of every text input and select element on the page when the element has a value (filled state) versus when it is empty. Uses CSS custom properties --input-background and --card-background defined by the active J1 skin.

skipAd.navbarSmoothScrollHandler

Intercepts clicks on same-page anchor links inside the top navigation bar and delegates scrolling to j1.scrollToAnchor() for a smooth animated scroll instead of the browser’s default instant jump.

Playlist Entry Object

The following table describes every field that a playlist entry can contain. Fields marked auto are filled in automatically by the module and you do not normally need to supply them.

Field Type Description

videoId

String

(Required) The 11-character YouTube video ID.

title

String

(Required) Video title as reported by the YouTube API.

watchDate

String

(Required / auto) ISO 8601 timestamp of the last watch event.

author

String

(auto) YouTube channel name. Populated once playback starts.

duration

Number

(auto) Video length in seconds. Populated from the durationchange event.

lastPosition

Number

(auto) Last saved playback position in seconds. 0 means start from beginning.

rating

Number

User star rating: 1–5, or 0 for no rating.

category

String

User-defined category (e.g. Music, Education).

tags

String

Comma-separated tag list used by the search index.

series

Number

Series number for episode-based playlists (0 = not part of a series).

episode

Number

Episode number within a series (0 = not applicable).

type

String

Content type (e.g. video, audioclip, podcast).

issueDate

String

Original publication date in ISO yyyy-mm-dd format. Free-text dates with US timezone abbreviations are normalised automatically.

infoLink

String

Optional URL to an external information page (must be HTTP or HTTPS).

videoLink

String

Optional URL to an alternative video source (must be HTTP or HTTPS).

description

String

Optional free-text description of the video.

Playlist JSON File Format

When you export your playlist the resulting JSON file uses the following structure. The same format is required when importing a file.

{
  "meta_data": {
    "exported_at": "2026-04-18T10:23:00.000Z",
    "count": 2
  },
  "playlist": [
    {
      "videoId":      "dQw4w9WgXcQ",
      "title":        "Never Gonna Give You Up",
      "author":       "Rick Astley",
      "watchDate":    "2026-04-18T09:00:00.000Z",
      "duration":     213,
      "lastPosition": 0,
      "rating":       5,
      "category":     "Music",
      "tags":         "pop, 80th, classic",
      "series":       0,
      "episode":      0,
      "type":         "videoclip",
      "issueDate":    "1987-07-27",
      "infoLink":     "",
      "videoLink":    "",
      "description":  ""
    }
  ]
}

Older exports may contain a plain JSON array (no meta_data wrapper). SkipAd can import both formats automatically.

Custom DOM Events

SkipAd fires and listens to several custom events on the document object. You can attach your own listeners to these events to extend the module’s behaviour.

Event name Description

videoLoad

Fired by the input handler when a valid video ID has been extracted from the URL input and playback is about to start. The event detail object contains { videoId: '…​' }.

videoPlayingStarted

Fired once when the VideoJS player transitions from waiting / loading into the playing state for the first time. Used internally to clear the URL input field after a video begins playing.

ytVideoTitleResolved

Fired when the YouTube IFrame API returns the video title and author after the player is ready. The event detail object contains { title: '…​', playerId: '…​' }.

Example — listen for a new video load:

document.addEventListener('videoLoad', (e) => {
  console.log('Loading video: ' + e.detail.videoId);
});

Player State Mapping

SkipAd translates YouTube IFrame API player states into standard VideoJS event names so that plugins and custom code can listen to familiar events regardless of the underlying playback technology.

YouTube state code VideoJS event Meaning

-1

loadstart

Video not started yet (unstarted).

0

ended

Playback has reached the end.

1

playing

Video is currently playing.

2

paused

Video has been paused.

3

waiting

Player is buffering.

Built-in Tag Genres

When editing a playlist entry, the tag selector groups common tags by genre to make it easy to categorise your videos. The following genres and their associated tags are built into the module.

Genre Tags

Beauty

beauty, makeup, skincare, hairstyle, fashion

Comedy

comedy, standup, memes

Education

education, learn, tutorial, study

Entertainment

entertainment, infotainment, edutainment, dance, show, musical, tv, mixed, cinema, podcast, magician, horror, ventriloquist, celebrity, series, competition, audioclip, videoclip

Music

music, concert, festival, cover, pop, k-pop, classic, rock, folk, traditional, latin, jazz, rap, singer, songwriter, original, emotional, romantic, 60th, 70th, 80th, 90th, remix, live

Gaming

game, gamer

Howto

howto, diy, tech, mounting, cooking, food

Product

product, reviews, tech, unboxing, ai, programming, gadgets

News

news, commentary, viral, trending

Fitness

fitness, health, sport, workout, gym, motivation

Business

business, finance, tech, ai, programming, gadgets

Logging and Development Mode

SkipAd uses the log4javascript library for structured logging. Log output is controlled by the env field of the adapter options.

development or dev

Full debug, info, and warn messages are written to the browser console, including a unique session ID and a precise timestamp for each message.

production (default)

Only ERROR-level messages are written to the console. All debug and info output is suppressed.

You can temporarily switch to development mode in your J1 adapter configuration to trace unexpected behaviour:

adapter:
  skipad:
    env: development

Never set env: development on a production site — it produces a large volume of console output and may expose internal state to end users.