Skip to content

Commit 70e0295

Browse files
committed
Added Google Tag Manager's Spotify Audio Tag HTML code.
1 parent 8f5b77c commit 70e0295

File tree

2 files changed

+237
-4
lines changed

2 files changed

+237
-4
lines changed

gtm/spotify-audio.json.md

Lines changed: 118 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Description
44

5-
- **Custom HTML Tag** for [***Google Tag Manager***](https://tagmanager.google.com) which is able detect which content is being played and how far it's been played, as integer values between 1 and 99 in percentage and as floting point values as seconds;
5+
- **Custom HTML Tag** for [***Google Tag Manager***](https://tagmanager.google.com) which is able detect which content is being played and how far it's been played, as integer values between 1 and 99 (in percentages) and as floating point values (in seconds);
66
- No actual human friendly information is availabe *(no **episode**, **track**, **artist**, **album**, **playlist** or **show name** will be captured even though it might be viable via **DOM Scraping**)*:
77
- Each content is identified by a [`Spotify URI`](https://developer.spotify.com/documentation/web-api/concepts/spotify-uris-ids), which consists in three parts:
88
- `{platform}` **:** `{content type}` **:** `{identifier}`;
@@ -74,7 +74,7 @@
7474

7575
---
7676

77-
## Pre-Configuration: Spotify's IFrame
77+
## Pre-Configuration: Spotify Player's IFrame
7878

7979
```html
8080
<iframe src="https://open.spotify.com/embed/track/3qN5qMTKyEEmiTZD38CmPA" width="300" height="380" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe>
@@ -91,6 +91,122 @@ spotifyPlayer.contentWindow.postMessage({
9191

9292
---
9393

94+
## Spotify Audio Custom HTML Tag for [Google Tag Manager](https://tagmanager.google.com)
95+
> For further implementation details, please visit the [Spotify's Community topic](https://community.spotify.com/t5/Spotify-for-Developers/Spotify-iFrame-tracking-via-GTM-Any-code/m-p/6945950) where this implementation was developed and published.
96+
97+
```html
98+
<script>
99+
// - The operation below is needed to ensure the list of
100+
// percentages to be detected as progress events follow
101+
// the correct format.
102+
function truncateRemoveInvalidValuesDeduplicateSort(arr) {
103+
var truncatedList = arr.map(function(element) {
104+
return Math.trunc(element);
105+
}).filter(function(element) {
106+
return element > 0 && element < 100;
107+
});
108+
var uniqueValuesObject = {};
109+
for (var i = 0; i < truncatedList.length; i++) {
110+
uniqueValuesObject[truncatedList[i]] = true;
111+
}
112+
var uniqueList = Object.keys(uniqueValuesObject).map(function(key) {
113+
return parseInt(key, 10);
114+
});
115+
uniqueList.sort(function(a, b) {
116+
return a - b;
117+
});
118+
return uniqueList;
119+
}
120+
121+
// - Edit the list below to setup progress events' percentage values:
122+
// (List with unique integer values ranging from 1 to 99, sorted in ascending order)
123+
var spotifyPercentagesToBeDetected = truncateRemoveInvalidValuesDeduplicateSort(
124+
[99, 20, 10.5, 60, 100, 40.05, 80, 90, 30, 50, 70, 0, 99.3]
125+
);
126+
// List is certainly [10, 20, 30, 40, 50, 60, 70, 80, 90, 99] after the cleansing.
127+
128+
// - Check if a calculated percentage value should or not be detected.
129+
function shouldPercentageBeDetected(percent, detectionList) {
130+
for (var i = 0; i < detectionList.length; i++) {
131+
if (percent >= detectionList[i] && !(detectionList[i+1] && detectionList[i+1] <= percent)) {
132+
return { check: true, value: detectionList[i] };
133+
}
134+
}
135+
return { check: false, value: undefined };
136+
}
137+
138+
var spotifyWasPaused = false;
139+
var spotifyAudioCompleted = false;
140+
var spotifyRegisteredProgress = [];
141+
var spotifyLastDuration = 0.0;
142+
var spotifyLastURI = '';
143+
window.addEventListener('message', function(event) {
144+
if (event.origin === 'https://open.spotify.com') {
145+
var audioPercent = Math.trunc((event.data.payload.position / event.data.payload.duration) * 100) || 0;
146+
var audioCurrentTime = (event.data.payload.position / 1000) || 0;
147+
var audioDuration = (event.data.payload.duration / 1000) || 0;
148+
var spotifyURI = event.data.payload.playingURI;
149+
var spotifyEvent = {
150+
event: 'spotifyEvent',
151+
audioPercent: audioPercent,
152+
audioCurrentTime: audioCurrentTime,
153+
audioDuration: audioDuration,
154+
spotifyURI: spotifyURI
155+
}
156+
// - Restart Playback Control Variables in case URI ou Duration has
157+
// changed (track change detection within playlist, album or artist).
158+
if ((spotifyURI && spotifyURI !== spotifyLastURI) || (spotifyURI && spotifyURI === spotifyLastURI && spotifyLastDuration !== audioDuration && spotifyLastDuration && audioDuration && Math.round(spotifyLastDuration) !== Math.round(audioDuration))) {
159+
spotifyAudioCompleted = false;
160+
spotifyWasPaused = false;
161+
spotifyRegisteredProgress = [];
162+
}
163+
// 1. Progress Events
164+
if (spotifyURI && shouldPercentageBeDetected(audioPercent, spotifyPercentagesToBeDetected).check) {
165+
if (!spotifyRegisteredProgress.includes(shouldPercentageBeDetected(audioPercent, spotifyPercentagesToBeDetected).value)) {
166+
spotifyRegisteredProgress.push(shouldPercentageBeDetected(audioPercent, spotifyPercentagesToBeDetected).value);
167+
spotifyEvent.audioStatus = 'progress';
168+
spotifyEvent.audioPercent = shouldPercentageBeDetected(audioPercent, spotifyPercentagesToBeDetected).value;
169+
dataLayer.push(spotifyEvent);
170+
spotifyLastURI = spotifyURI;
171+
if (audioDuration) spotifyLastDuration = audioDuration;
172+
}
173+
}
174+
// 2. Playback updates
175+
// 2.1. Playback Start
176+
if (spotifyURI && event.data.type === 'playback_started') {
177+
spotifyEvent.audioStatus = 'playback_started';
178+
dataLayer.push(spotifyEvent);
179+
spotifyLastURI = spotifyURI;
180+
// 2.2. Playback Paused
181+
} else if (spotifyURI && event.data.type === 'playback_update' && event.data.payload.isPaused && audioCurrentTime && !spotifyWasPaused) {
182+
spotifyEvent.audioStatus = 'playback_paused';
183+
dataLayer.push(spotifyEvent);
184+
spotifyLastURI = spotifyURI;
185+
if (audioDuration) spotifyLastDuration = audioDuration;
186+
spotifyWasPaused = true;
187+
// 2.3. Playback Resumed
188+
} else if (spotifyURI && event.data.type === 'playback_update' && !event.data.payload.isPaused && spotifyWasPaused && event.data.payload.position) {
189+
spotifyEvent.audioStatus = 'playback_resumed';
190+
dataLayer.push(spotifyEvent);
191+
spotifyLastURI = spotifyURI;
192+
if (audioDuration) spotifyLastDuration = audioDuration;
193+
spotifyWasPaused = false;
194+
// 2.4. Complete
195+
} else if (spotifyURI && event.data.type === 'playback_update' && audioDuration === audioCurrentTime && !spotifyAudioCompleted) {
196+
spotifyEvent.audioStatus = 'complete';
197+
spotifyEvent.audioPercent = 100;
198+
dataLayer.push(spotifyEvent);
199+
spotifyLastURI = spotifyURI;
200+
if (audioDuration) spotifyLastDuration = audioDuration;
201+
spotifyAudioCompleted = true;
202+
}
203+
}
204+
}, false);
205+
</script>
206+
```
207+
208+
---
209+
94210
## Importing **Tags**, **Triggers** and **Variables** in `Google Tag Manager`
95211

96212
- Save the `JSON` below to a file and **import** it in [*Google Tag Manager*](https://tagmanager.google.com):

gtm/spotify-audio.json.pt-br.md

Lines changed: 119 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Descrição
44

5-
- **Tag HTML Personalizado** para [***Google Tag Manager***](https://tagmanager.google.com) que é capaz de detectar qual conteúdo está sendo executado e até que ponto ocorreu a execução, sob a forma de valores inteiros entre 1 and 99 como porcentagem e sob a forma de valores reais em segundos;
5+
- **Tag HTML Personalizado** para [***Google Tag Manager***](https://tagmanager.google.com) que é capaz de detectar qual conteúdo está sendo executado e até que ponto ocorreu a execução, sob a forma de valores inteiros entre 1 and 99 (em porcentagens) e sob a forma de valores reais (em segundos);
66
- Nenhuma informação humanamente amigável está disponível *(nenhum nome de **episódio**, **faixa**, **artista**, **álbum**, **lista de reprodução** ou **show** será capturada, ainda que talvez seja possível por **Raspagem de DOM**)*:
77
- Cada conteúdo é identificado por uma [`URI do Spotify`](https://developer.spotify.com/documentation/web-api/concepts/spotify-uris-ids), que consiste em três partes:
88
- `{plataforma}` **:** `{tipo de conteúd}` **:** `{identificador}`;
@@ -74,7 +74,7 @@
7474

7575
---
7676

77-
## Pré-Configuração: IFrame do Spotify
77+
## Pré-Configuração: IFrame do Reprodutor Spotify
7878

7979
```html
8080
<iframe src="https://open.spotify.com/embed/track/3qN5qMTKyEEmiTZD38CmPA" width="300" height="380" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe>
@@ -91,6 +91,123 @@ reprodutorSpotify.contentWindow.postMessage({
9191

9292
---
9393

94+
## Tag HTML Personalizado de Spotify Audio para [Google Tag Manager](https://tagmanager.google.com)
95+
96+
> Para maiores detalhes de implementação, favor visitar o [tópico da Comunidade do Spotify](https://community.spotify.com/t5/Spotify-for-Developers/Spotify-iFrame-tracking-via-GTM-Any-code/m-p/6945950) onde esta implementação foi desenvolvida e publicada.
97+
98+
```html
99+
<script>
100+
// - A operação abaixo é necessária para garantir que a
101+
// a lista de porcentagens a serem detectadas como eventos
102+
// de progresso siga o formato correto.
103+
function truncateRemoveInvalidValuesDeduplicateSort(arr) {
104+
var truncatedList = arr.map(function(element) {
105+
return Math.trunc(element);
106+
}).filter(function(element) {
107+
return element > 0 && element < 100;
108+
});
109+
var uniqueValuesObject = {};
110+
for (var i = 0; i < truncatedList.length; i++) {
111+
uniqueValuesObject[truncatedList[i]] = true;
112+
}
113+
var uniqueList = Object.keys(uniqueValuesObject).map(function(key) {
114+
return parseInt(key, 10);
115+
});
116+
uniqueList.sort(function(a, b) {
117+
return a - b;
118+
});
119+
return uniqueList;
120+
}
121+
122+
// - Edite a lista abaixo para configurar os valores de porcentagem dos eventos de progresso:
123+
// (Lista de valores inteiros únicos de 1 a 99, ordenados em ordem crescente)
124+
var spotifyPercentagesToBeDetected = truncateRemoveInvalidValuesDeduplicateSort(
125+
[99, 20, 10.5, 60, 100, 40.05, 80, 90, 30, 50, 70, 0, 99.3]
126+
);
127+
// A lista é certamente [10, 20, 30, 40, 50, 60, 70, 80, 90, 99] uma vez processada.
128+
129+
// - Checa se um valor calculado de porcentagem deve ser detectado ou não.
130+
function shouldPercentageBeDetected(percent, detectionList) {
131+
for (var i = 0; i < detectionList.length; i++) {
132+
if (percent >= detectionList[i] && !(detectionList[i+1] && detectionList[i+1] <= percent)) {
133+
return { check: true, value: detectionList[i] };
134+
}
135+
}
136+
return { check: false, value: undefined };
137+
}
138+
139+
var spotifyWasPaused = false;
140+
var spotifyAudioCompleted = false;
141+
var spotifyRegisteredProgress = [];
142+
var spotifyLastDuration = 0.0;
143+
var spotifyLastURI = '';
144+
window.addEventListener('message', function(event) {
145+
if (event.origin === 'https://open.spotify.com') {
146+
var audioPercent = Math.trunc((event.data.payload.position / event.data.payload.duration) * 100) || 0;
147+
var audioCurrentTime = (event.data.payload.position / 1000) || 0;
148+
var audioDuration = (event.data.payload.duration / 1000) || 0;
149+
var spotifyURI = event.data.payload.playingURI;
150+
var spotifyEvent = {
151+
event: 'spotifyEvent',
152+
audioPercent: audioPercent,
153+
audioCurrentTime: audioCurrentTime,
154+
audioDuration: audioDuration,
155+
spotifyURI: spotifyURI
156+
}
157+
// - Reiniciar as Variáveis de Controle de Reprodução caso a URI ou a Duração
158+
// tenham sido alteradas (detecção da troca de faixas dentro de lista de reprodução, álbum ou artista).
159+
if ((spotifyURI && spotifyURI !== spotifyLastURI) || (spotifyURI && spotifyURI === spotifyLastURI && spotifyLastDuration !== audioDuration && spotifyLastDuration && audioDuration && Math.round(spotifyLastDuration) !== Math.round(audioDuration))) {
160+
spotifyAudioCompleted = false;
161+
spotifyWasPaused = false;
162+
spotifyRegisteredProgress = [];
163+
}
164+
// 1. Eventos de Progresso
165+
if (spotifyURI && shouldPercentageBeDetected(audioPercent, spotifyPercentagesToBeDetected).check) {
166+
if (!spotifyRegisteredProgress.includes(shouldPercentageBeDetected(audioPercent, spotifyPercentagesToBeDetected).value)) {
167+
spotifyRegisteredProgress.push(shouldPercentageBeDetected(audioPercent, spotifyPercentagesToBeDetected).value);
168+
spotifyEvent.audioStatus = 'progress';
169+
spotifyEvent.audioPercent = shouldPercentageBeDetected(audioPercent, spotifyPercentagesToBeDetected).value;
170+
dataLayer.push(spotifyEvent);
171+
spotifyLastURI = spotifyURI;
172+
if (audioDuration) spotifyLastDuration = audioDuration;
173+
}
174+
}
175+
// 2. Atualização de Reprodução
176+
// 2.1. Reprodução Iniciada
177+
if (spotifyURI && event.data.type === 'playback_started') {
178+
spotifyEvent.audioStatus = 'playback_started';
179+
dataLayer.push(spotifyEvent);
180+
spotifyLastURI = spotifyURI;
181+
// 2.2. Reprodução Pausada
182+
} else if (spotifyURI && event.data.type === 'playback_update' && event.data.payload.isPaused && audioCurrentTime && !spotifyWasPaused) {
183+
spotifyEvent.audioStatus = 'playback_paused';
184+
dataLayer.push(spotifyEvent);
185+
spotifyLastURI = spotifyURI;
186+
if (audioDuration) spotifyLastDuration = audioDuration;
187+
spotifyWasPaused = true;
188+
// 2.3. Reprodução Retomada
189+
} else if (spotifyURI && event.data.type === 'playback_update' && !event.data.payload.isPaused && spotifyWasPaused && event.data.payload.position) {
190+
spotifyEvent.audioStatus = 'playback_resumed';
191+
dataLayer.push(spotifyEvent);
192+
spotifyLastURI = spotifyURI;
193+
if (audioDuration) spotifyLastDuration = audioDuration;
194+
spotifyWasPaused = false;
195+
// 2.4. Completo
196+
} else if (spotifyURI && event.data.type === 'playback_update' && audioDuration === audioCurrentTime && !spotifyAudioCompleted) {
197+
spotifyEvent.audioStatus = 'complete';
198+
spotifyEvent.audioPercent = 100;
199+
dataLayer.push(spotifyEvent);
200+
spotifyLastURI = spotifyURI;
201+
if (audioDuration) spotifyLastDuration = audioDuration;
202+
spotifyAudioCompleted = true;
203+
}
204+
}
205+
}, false);
206+
</script>
207+
```
208+
209+
---
210+
94211
## Importando **Tags**, **Acionadores** and **Variáveis** no `Google Tag Manager`
95212

96213
- Salve o `JSON` abaixo em um arquivo e **importe-o** no [*Google Tag Manager*](https://tagmanager.google.com):

0 commit comments

Comments
 (0)