Click to play/pause video without controls

<video src="video.mp4" onclick="this.paused ? this.play() : this.pause();"></video>

I don’t want to show video controls but still allow play/pause by clicking on the video. Can someone please advice if this is possible, and if so, how?

1 Like

This is what’s the following solution does and more Youtube macro - looking for feedbacks - #12 by telumire

Thank you! I hope this works but I’m not very familiar with TW yet.

I imported the macro+demo.json and replaced the macro with the updated version. I entered video.mp4 into the url field of the “Demo macro video” tiddler but it doesn’t show anything. I also tried with file:///path/to/video.mp4 which also did not work.

This is my tiddler with the video (a local file):

<video src="video.mp4"></video>

Sorry, @devon assumed you were linking to YouTube videos on line.

By the way welcome to talk.tiddlywiki, even silly questions are allowed, but I am sure we can find you a perfect solution.

If you can get a free account to host your videos online then embed that video, visitors to your site will not use too much of your websites bandwidth.

If the video is local to your device, a file, and only for you another approach is using the external content. There are plenty of existing discussions about this for example How to embed video like [img[xxx]]

  • If Its one small video just import it and transclude that tiddler where you want it, should work (untested by me today)

No problem and thanks for your suggestions. I’m only using this offline for myself so I have set “excludeEventAttributes” to false so that the onscript attribute is not removed.

Here’s one way to do it:

<iframe style="aspect-ratio:16/9;width:100%;border:0;" srcdoc='
<style>
video{
position:fixed;
inset:0;
width:100%;
height:100%;
object-fit:contain;
background: black;
}
</style>
<video src="your video url here." onclick="this.controls=0; this.paused || this.ended ? this.play() : this.pause();" >'/>
1 Like

Isn’t this “sanitised” by tiddlywiki

That seems to work pretty well, thank you! The only limitation is that the iframe requires the aspect ratio of the video.

TW doesn’t seem to check the content of the srcdoc attribute for unsafe attributes.

yes, IMO the only other way to solve this particular problem is to create a custom widget to be able to handle the javascript (since you can’t change the state of a video without controls without using js). Note that the aspect-ratio attribute is not strictly needed if you do not care about the size of the black bars around the video, you can put an arbitrary height and the video will adapt to it.

I asked chatgpt to help with that, here’s the code it gave me:

/*\
title: $:/core/modules/widgets/video.js
type: application/javascript
module-type: widget

The video widget embeds a video from an external URI, a local tiddler title, or base64-encoded video data contained within a tiddler, with custom play/pause on click.

Usage:
<$video src="TiddlerTitle" width="320" height="240"></$video>

Attributes:
src: The source of the video. Can be a tiddler title, a URL to an external video, or a tiddler with base64-encoded video data.
width: Width of the video element.
height: Height of the video element.
controls: If true, shows video controls. Custom play/pause functionality allows controls to be hidden but still provides user interaction.

\*/
(function(){

/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";

var Widget = require("$:/core/modules/widgets/widget.js").widget;

var VideoWidget = function(parseTreeNode,options) {
    this.initialise(parseTreeNode,options);
};

/*
Inherit from the base widget class
*/
VideoWidget.prototype = new Widget();

/*
Render this widget into the DOM
*/
VideoWidget.prototype.render = function(parent,nextSibling) {
    this.parentDomNode = parent;
    this.computeAttributes();
    this.execute();

    // Create the video element
    var videoNode = this.document.createElement("video");
    this.assignAttributes(videoNode);

    // Handle video source
    var tiddler = this.wiki.getTiddler(this.src);
    if(tiddler) {
        if(tiddler.fields._canonical_uri) {
            this.createSourceElement(videoNode, tiddler.fields._canonical_uri, tiddler.fields.type);
        } else if(tiddler.fields.text) {
            var base64src = "data:" + tiddler.fields.type + ";base64," + tiddler.fields.text;
            this.createSourceElement(videoNode, base64src, tiddler.fields.type);
        }
    } else if(this.src) {
        this.createSourceElement(videoNode, this.src);
    }

    // Custom play/pause on click
    this.addPlayPauseOnClick(videoNode);

    // Insert the video element
    parent.insertBefore(videoNode, nextSibling);
    this.domNodes.push(videoNode);
};

VideoWidget.prototype.assignAttributes = function(videoNode) {
    videoNode.setAttribute("width", this.width);
    videoNode.setAttribute("height", this.height);
    if(this.controls === "true") {
        videoNode.setAttribute("controls", "");
    }
};

VideoWidget.prototype.createSourceElement = function(videoNode, src, type) {
    var sourceNode = this.document.createElement("source");
    sourceNode.setAttribute("src", src);
    if(type) {
        sourceNode.setAttribute("type", type);
    }
    videoNode.appendChild(sourceNode);
};

VideoWidget.prototype.addPlayPauseOnClick = function(videoNode) {
    videoNode.addEventListener("click", function() {
        if (this.paused || this.ended) {
            this.play();
        } else {
            this.pause();
        }
    });
};

/*
Compute the internal state of the widget
*/
VideoWidget.prototype.execute = function() {
    this.src = this.getAttribute("src");
    this.width = this.getAttribute("width", "320");
    this.height = this.getAttribute("height", "240");
    this.controls = this.getAttribute("controls", "false");
};

/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
VideoWidget.prototype.refresh = function(changedTiddlers) {
    var changedAttributes = this.computeAttributes();
    if(changedAttributes.src || changedAttributes.width || changedAttributes.height || changedAttributes.controls || changedTiddlers[this.src]) {
        this.refreshSelf();
        return true;
    }
    return false;
};

exports.video = VideoWidget;

})();

Seems to work, but probably needs to be tweaked to be good code. Would be nice to add support for subtitles synchronisations (translations, timestamps, etc), read aloud voice over for blind people, binding the progress to a field, custom player buttons, themes, playlists, embeding videos url from popular websites such as youtube, etc. Maybe one day, if I manage to actually understand how widgets works lol

Demo: Video widget — a non-linear personal web notebook (tiddlyhost.com)

I have started writing widgets and filter operators with javascript and chat gpt as well. Eg [sum-field[fieldname]] the input is titles. And [extend[qtyfield],[ pricefield]]

  • it is importiant before we do this and distribute them, we make sure it cant already be done with tiddlyscript, unless it is very messy, fills a gap or has an advantage.
  • also it is better to build general solitions that can satisfy “specific needs” with defaults and parameters.
  • if too many proliferate it could get messy.
  • there are times when it should move to the core.
1 Like