11import dayjs , { Dayjs } from "dayjs" ;
22import isToday from 'dayjs/plugin/isToday.js' ;
33import {
4+ AcceptableTemporalDuringReference ,
45 PlayObject ,
56 SCROBBLE_TS_SOC_END ,
67 SCROBBLE_TS_SOC_START ,
78 ScrobbleTsSOC ,
89 TA_CLOSE ,
10+ TA_DEFAULT_ACCURACY ,
11+ TA_DURING ,
912 TA_EXACT ,
1013 TA_FUZZY ,
1114 TA_NONE ,
@@ -38,19 +41,18 @@ export const temporalPlayComparisonSummary = (data: TemporalPlayComparison, exis
3841 parts . push ( `Play Diff: ${ formatNumber ( data . date . diff , { toFixed : 0 } ) } s (Needed <${ data . date . threshold } s)` )
3942 }
4043 if ( data . date . fuzzyDurationDiff !== undefined ) {
41- parts . push ( `Fuzzy Duration Diff: ${ formatNumber ( data . date . fuzzyDurationDiff , { toFixed : 0 } ) } s (Needed <= 10s )` ) ;
44+ parts . push ( `Fuzzy Duration Diff: ${ formatNumber ( data . date . fuzzyDurationDiff , { toFixed : 0 } ) } s (Needed <= ${ data . date . fuzzyDiffThreshold } s )` ) ;
4245 }
4346 if ( data . date . fuzzyListenedDiff !== undefined ) {
44- parts . push ( `Fuzzy Listened Diff: ${ formatNumber ( data . date . fuzzyDurationDiff , { toFixed : 0 } ) } s (Needed <= 10s )` ) ;
47+ parts . push ( `Fuzzy Listened Diff: ${ formatNumber ( data . date . fuzzyDurationDiff , { toFixed : 0 } ) } s (Needed <= ${ data . date . fuzzyDiffThreshold } s )` ) ;
4548 }
46- if ( data . range !== undefined ) {
47- if ( data . range === false ) {
48- parts . push ( 'Candidate not played during Existing tracked listening' ) ;
49- } else {
50- parts . push ( `Candidate played during tracked listening range from Existing ${ data . range [ 0 ] . timestamp . format ( 'HH:mm:ssZ' ) } => ${ data . range [ 1 ] . timestamp . format ( 'HH:mm:ssZ' ) } ` ) ;
51- }
52- } else {
49+
50+ if ( data . range === undefined ) {
5351 parts . push ( 'Range Comparison N/A' ) ;
52+ } else if ( data . range . type === 'none' ) {
53+ parts . push ( `Candidate not played during Existing ${ data . duringReferences . join ( ' or ' ) } ` ) ;
54+ } else {
55+ parts . push ( `Candidate played during tracked listening range from Existing "${ data . range . type } " ${ data . range . timestamps [ 0 ] . format ( 'HH:mm:ssZ' ) } => ${ data . range . timestamps [ 1 ] . format ( 'HH:mm:ssZ' ) } ` ) ;
5456 }
5557 return parts . join ( ' | ' ) ;
5658}
@@ -59,14 +61,11 @@ export interface TemporalPlayComparisonOptions {
5961 diffThreshold ?: number ,
6062 fuzzyDuration ?: boolean ,
6163 fuzzyDiffThreshold ?: number
62- useListRanges ?: boolean
64+ duringReferences ?: AcceptableTemporalDuringReference
6365}
6466
6567export const comparePlayTemporally = ( existingPlay : PlayObject , candidatePlay : PlayObject , options : TemporalPlayComparisonOptions = { } ) : TemporalPlayComparison => {
6668
67- const result : TemporalPlayComparison = {
68- match : TA_NONE
69- } ;
7069
7170 const {
7271 meta : {
@@ -103,9 +102,14 @@ export const comparePlayTemporally = (existingPlay: PlayObject, candidatePlay: P
103102 diffThreshold = lowGranularitySources . some ( x => x . toLocaleLowerCase ( ) === source ) ? 60 : 10 ,
104103 fuzzyDuration = false ,
105104 fuzzyDiffThreshold = 10 ,
106- useListRanges = true ,
105+ duringReferences = [ 'range' ]
107106 } = options ;
108107
108+ const result : TemporalPlayComparison = {
109+ match : TA_NONE ,
110+ duringReferences
111+ } ;
112+
109113 // cant compare!
110114 if ( existingTsSOCDate === undefined || candidateTsSOCDate === undefined ) {
111115 return result ;
@@ -120,7 +124,8 @@ export const comparePlayTemporally = (existingPlay: PlayObject, candidatePlay: P
120124 const scrobblePlayDiff = Math . abs ( existingTsSOCDate . unix ( ) - candidateTsSOCDate . unix ( ) ) ;
121125 result . date = {
122126 threshold : diffThreshold ,
123- diff : scrobblePlayDiff
127+ diff : scrobblePlayDiff ,
128+ fuzzyDiffThreshold
124129 } ;
125130
126131 if ( scrobblePlayDiff <= 1 ) {
@@ -129,22 +134,50 @@ export const comparePlayTemporally = (existingPlay: PlayObject, candidatePlay: P
129134 result . match = TA_CLOSE ;
130135 }
131136
132- if ( useListRanges && existingRanges !== undefined ) {
133- // since we know when the existing track was listened to
134- // we can check if the new track play date took place while the existing one was being listened to
135- // which would indicate (assuming same source) the new track is a duplicate
136- for ( const range of existingRanges ) {
137- if ( candidateTsSOCDate . isBetween ( range . start . timestamp , range . end . timestamp ) ) {
138- result . range = range ;
139- if ( ! temporalAccuracyIsAtLeast ( TA_CLOSE , result . match ) ) {
140- result . match = TA_CLOSE ;
137+ if ( result . match !== TA_NONE ) {
138+ return result ;
139+ }
140+
141+ if ( duringReferences . length > 0 ) {
142+
143+ if ( duringReferences . includes ( 'range' ) && existingRanges !== undefined ) {
144+ // since we know when the existing track was listened to
145+ // we can check if the new track play date took place while the existing one was being listened to
146+ // which would indicate (assuming same source) the new track is a duplicate
147+ for ( const range of existingRanges ) {
148+ if ( candidateTsSOCDate . isBetween ( range . start . timestamp , range . end . timestamp ) ) {
149+ result . range = {
150+ type : 'range' ,
151+ timestamps : [ range . start . timestamp , range . end . timestamp ]
152+ }
153+ result . match = TA_DURING ;
154+ return result ;
155+ }
156+ }
157+ }
158+
159+ if ( duringReferences . includes ( 'listenedFor' ) && existingPlay . data . listenedFor !== undefined ) {
160+ if ( candidateTsSOCDate . isBetween ( existingTsSOCDate , existingTsSOCDate . add ( existingPlay . data . listenedFor , 's' ) ) ) {
161+ result . match = TA_DURING ;
162+ result . range = {
163+ type : 'listenedFor' ,
164+ timestamps : [ existingTsSOCDate , existingTsSOCDate . add ( existingPlay . data . listenedFor , 's' ) ]
141165 }
142- break ;
166+ return result ;
143167 }
144168 }
145- if ( result . range === undefined ) {
146- result . range = false ;
169+
170+ if ( duringReferences . includes ( 'duration' ) && existingPlay . data . duration !== undefined ) {
171+ if ( candidateTsSOCDate . isBetween ( existingTsSOCDate , existingTsSOCDate . add ( existingPlay . data . duration , 's' ) ) ) {
172+ result . match = TA_DURING ;
173+ result . range = {
174+ type : 'duration' ,
175+ timestamps : [ existingTsSOCDate , existingTsSOCDate . add ( existingPlay . data . duration , 's' ) ]
176+ }
177+ return result ;
178+ }
147179 }
180+
148181 }
149182
150183 // if the source has a duration its possible one play was scrobbled at the beginning of the track and the other at the end
@@ -199,15 +232,7 @@ export const timePassesScrobbleThreshold = (thresholds: ScrobbleThresholds, seco
199232 }
200233}
201234
202- export const temporalAccuracyIsAtLeast = ( expected : TemporalAccuracy , found : TemporalAccuracy ) : boolean => {
203- if ( typeof expected === 'number' ) {
204- if ( typeof found === 'number' ) {
205- return found <= expected ;
206- }
207- return false ;
208- }
209- return found === false ;
210- }
235+ export const hasAcceptableTemporalAccuracy = ( found : TemporalAccuracy , expected : TemporalAccuracy [ ] = TA_DEFAULT_ACCURACY ) : boolean => expected . includes ( found ) ;
211236
212237export const temporalAccuracyToString = ( acc : TemporalAccuracy ) : string => {
213238 switch ( acc ) {
@@ -217,7 +242,9 @@ export const temporalAccuracyToString = (acc: TemporalAccuracy): string => {
217242 return 'close' ;
218243 case 3 :
219244 return 'fuzzy' ;
220- case false :
245+ case 4 :
246+ return 'during' ;
247+ case 99 :
221248 return 'no correlation' ;
222249 }
223250}
0 commit comments