Skip to content

Commit

Permalink
Autoplay detection library: initial release && Teads Bid Adapter: det…
Browse files Browse the repository at this point in the history
…ect autoplay (prebid#11222)

* Add autoplay library

* Filter out bids with needAutoplay if autoplay is disabled

* Refactoring + add test

* Simplify logic for filtering bids

* Start detection in autoplay.js directly
  • Loading branch information
github-matthieu-wipliez authored and dgirardi committed Mar 20, 2024
1 parent 6d34941 commit 0cdc6ec
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 8 deletions.
42 changes: 42 additions & 0 deletions libraries/autoplayDetection/autoplay.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
let autoplayEnabled = null;

/**
* Note: this function returns true if detection is not done yet. This is by design: if autoplay is not allowed,
* the call to video.play() will fail immediately, otherwise it may not terminate.
* @returns true if autoplay is not forbidden
*/
export const isAutoplayEnabled = () => autoplayEnabled !== false;

// generated with:
// ask ChatGPT for a 160x90 black PNG image (1/8th the size of 720p)
//
// encode with:
// ffmpeg -i black_image_160x90.png -r 1 -c:v libx264 -bsf:v 'filter_units=remove_types=6' -pix_fmt yuv420p autoplay.mp4
// this creates a 1 second long, 1 fps YUV 4:2:0 video encoded with H.264 without encoder details.
//
// followed by:
// echo "data:video/mp4;base64,$(base64 -i autoplay.mp4)"

const autoplayVideoUrl =
'data:video/mp4;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAAADxtZGF0AAAAMGWIhAAV//73ye/Apuvb3rW/k89I/Cy3PsIqP39atohOSV14BYa1heKCYgALQC5K4QAAAwZtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAD6AABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACMHRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAEAAAAAAAAD6AAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAoAAAAFoAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAA+gAAAAAAAEAAAAAAahtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAEAAAABAAFXEAAAAAAAtaGRscgAAAAAAAAAAdmlkZQAAAAAAAAAAAAAAAFZpZGVvSGFuZGxlcgAAAAFTbWluZgAAABR2bWhkAAAAAQAAAAAAAAAAAAAAJGRpbmYAAAAcZHJlZgAAAAAAAAABAAAADHVybCAAAAABAAABE3N0YmwAAACvc3RzZAAAAAAAAAABAAAAn2F2YzEAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAoABaAEgAAABIAAAAAAAAAAEVTGF2YzYwLjMxLjEwMiBsaWJ4MjY0AAAAAAAAAAAAAAAY//8AAAA1YXZjQwFkAAr/4QAYZ2QACqzZQo35IQAAAwABAAADAAIPEiWWAQAGaOvjyyLA/fj4AAAAABRidHJ0AAAAAAAAAaAAAAGgAAAAGHN0dHMAAAAAAAAAAQAAAAEAAEAAAAAAHHN0c2MAAAAAAAAAAQAAAAEAAAABAAAAAQAAABRzdHN6AAAAAAAAADQAAAABAAAAFHN0Y28AAAAAAAAAAQAAADAAAABidWR0YQAAAFptZXRhAAAAAAAAACFoZGxyAAAAAAAAAABtZGlyYXBwbAAAAAAAAAAAAAAAAC1pbHN0AAAAJal0b28AAAAdZGF0YQAAAAEAAAAATGF2ZjYwLjE2LjEwMA==';

function startDetection() {
// we create an HTMLVideoElement muted and not displayed in which we try to play a one frame video
const videoElement = document.createElement('video');
videoElement.src = autoplayVideoUrl;
videoElement.setAttribute('playsinline', 'true');
videoElement.muted = true;

videoElement
.play()
.then(() => {
autoplayEnabled = true;
videoElement.pause();
})
.catch(() => {
autoplayEnabled = false;
});
}

// starts detection as soon as this library is loaded
startDetection();
18 changes: 12 additions & 6 deletions modules/teadsBidAdapter.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {getValue, logError, deepAccess, parseSizesInput, isArray, getBidIdParameter} from '../src/utils.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
import {getStorageManager} from '../src/storageManager.js';
import {isAutoplayEnabled} from '../libraries/autoplayDetection/autoplay.js';

/**
* @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest
Expand Down Expand Up @@ -120,11 +121,18 @@ export const spec = {
* @return {Bid[]} An array of bids which were nested inside the server.
*/
interpretResponse: function(serverResponse, bidderRequest) {
const bidResponses = [];
serverResponse = serverResponse.body;

if (serverResponse.responses) {
serverResponse.responses.forEach(function (bid) {
if (!serverResponse.responses) {
return [];
}

const autoplayEnabled = isAutoplayEnabled();
return serverResponse.responses
.filter((bid) =>
// ignore this bid if it requires autoplay but it is not enabled on this browser
!bid.needAutoplay || autoplayEnabled
).map((bid) => {
const bidResponse = {
cpm: bid.cpm,
width: bid.width,
Expand All @@ -146,10 +154,8 @@ export const spec = {
if (bid?.ext?.dsa) {
bidResponse.meta.dsa = bid.ext.dsa;
}
bidResponses.push(bidResponse);
return bidResponse;
});
}
return bidResponses;
}
};

Expand Down
70 changes: 68 additions & 2 deletions test/spec/modules/teadsBidAdapter_spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {expect} from 'chai';
import {spec, storage} from 'modules/teadsBidAdapter.js';
import {newBidder} from 'src/adapters/bidderFactory.js';
import { off } from '../../../src/events';
import * as autoplay from 'libraries/autoplayDetection/autoplay.js'

const ENDPOINT = 'https://a.teads.tv/hb/bid-request';
const AD_SCRIPT = '<script type="text/javascript" class="teads" async="true" src="https://a.teads.tv/hb/getAdSettings"></script>"';
Expand Down Expand Up @@ -1059,7 +1059,8 @@ describe('teadsBidAdapter', () => {
'ttl': 360,
'width': 300,
'creativeId': 'er2ee',
'placementId': 34
'placementId': 34,
'needAutoplay': true
}, {
'ad': AD_SCRIPT,
'cpm': 0.5,
Expand All @@ -1070,6 +1071,7 @@ describe('teadsBidAdapter', () => {
'width': 350,
'creativeId': 'fs3ff',
'placementId': 34,
'needAutoplay': false,
'dealId': 'ABC_123',
'ext': {
'dsa': {
Expand Down Expand Up @@ -1132,6 +1134,70 @@ describe('teadsBidAdapter', () => {
expect(result).to.eql(expectedResponse);
});

it('should filter bid responses with needAutoplay:true when autoplay is disabled', function() {
let bids = {
'body': {
'responses': [{
'ad': AD_SCRIPT,
'cpm': 0.5,
'currency': 'USD',
'height': 250,
'bidId': '3ede2a3fa0db94',
'ttl': 360,
'width': 300,
'creativeId': 'er2ee',
'placementId': 34,
'needAutoplay': true
}, {
'ad': AD_SCRIPT,
'cpm': 0.5,
'currency': 'USD',
'height': 200,
'bidId': '4fef3b4gb1ec15',
'ttl': 360,
'width': 350,
'creativeId': 'fs3ff',
'placementId': 34,
'needAutoplay': false
}, {
'ad': AD_SCRIPT,
'cpm': 0.7,
'currency': 'USD',
'height': 600,
'bidId': 'a987fbc961d',
'ttl': 12,
'width': 300,
'creativeId': 'awuygfd',
'placementId': 12,
'needAutoplay': true
}]
}
};
let expectedResponse = [{
'cpm': 0.5,
'width': 350,
'height': 200,
'currency': 'USD',
'netRevenue': true,
'meta': {
advertiserDomains: [],
},
'ttl': 360,
'ad': AD_SCRIPT,
'requestId': '4fef3b4gb1ec15',
'creativeId': 'fs3ff',
'placementId': 34
}
]
;

const isAutoplayEnabledStub = sinon.stub(autoplay, 'isAutoplayEnabled');
isAutoplayEnabledStub.returns(false);
let result = spec.interpretResponse(bids);
isAutoplayEnabledStub.restore();
expect(result).to.eql(expectedResponse);
});

it('handles nobid responses', function() {
let bids = {
'body': {
Expand Down

0 comments on commit 0cdc6ec

Please sign in to comment.