@checksub_team/peaks_timeline 2.3.1 → 2.4.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@checksub_team/peaks_timeline",
3
- "version": "2.3.1",
3
+ "version": "2.4.0-alpha.0",
4
4
  "description": "JavaScript UI component for displaying audio waveforms",
5
5
  "main": "./peaks.js",
6
6
  "types": "./peaks.js.d.ts",
package/peaks.js CHANGED
@@ -14573,7 +14573,9 @@ module.exports = function (Data, Utils) {
14573
14573
  }
14574
14574
  DataRetriever.prototype.retrieveData = function (source) {
14575
14575
  this._retrieveDataByUrl(source, source.previewUrl);
14576
- this._retrieveDataByUrl(source, source.binaryUrl);
14576
+ if (!source.waveformData) {
14577
+ this._retrieveDataByUrl(source, source.binaryUrl);
14578
+ }
14577
14579
  };
14578
14580
  DataRetriever.prototype._retrieveDataByUrl = function (source, url) {
14579
14581
  if (url && url !== '') {
@@ -18833,6 +18835,92 @@ module.exports = function (WaveformBuilder, WaveformShape, Loader, Invoker, Util
18833
18835
  this._createAudioPreview(preview, redraw);
18834
18836
  }
18835
18837
  };
18838
+ SourceGroup.prototype._getWaveformData = function () {
18839
+ return this._source && this._source.waveformData ? this._source.waveformData : null;
18840
+ };
18841
+ SourceGroup.prototype._hasWaveformData = function () {
18842
+ return Boolean(this._getWaveformData());
18843
+ };
18844
+ SourceGroup.prototype._removeWaveformDataPreview = function () {
18845
+ if (!this._previewList || this._previewList.length === 0) {
18846
+ return;
18847
+ }
18848
+ var removed = false;
18849
+ this._previewList = this._previewList.filter(function (preview) {
18850
+ if (preview && preview.type === 'waveformData') {
18851
+ if (preview.group && typeof preview.group.destroy === 'function') {
18852
+ preview.group.destroy();
18853
+ }
18854
+ removed = true;
18855
+ return false;
18856
+ }
18857
+ return true;
18858
+ });
18859
+ if (removed) {
18860
+ this._scheduleBatchDraw();
18861
+ }
18862
+ };
18863
+ SourceGroup.prototype.addWaveformDataPreview = function (redraw) {
18864
+ if (!this._hasWaveformData()) {
18865
+ return;
18866
+ }
18867
+ if (!this._unwrap) {
18868
+ this._unwrap = this._createUnwrap();
18869
+ }
18870
+ var existing = this._previewList.filter(function (preview) {
18871
+ return preview && preview.type === 'waveformData';
18872
+ });
18873
+ if (existing.length > 0) {
18874
+ if (redraw) {
18875
+ this._scheduleBatchDraw();
18876
+ }
18877
+ return;
18878
+ }
18879
+ var preview = {
18880
+ type: 'waveformData',
18881
+ group: new Konva.Group({
18882
+ height: this._source.previewUrl ? this._source.binaryHeight : this._unwrappedHeight,
18883
+ listening: false
18884
+ })
18885
+ };
18886
+ var self = this;
18887
+ var waveform = new WaveformShape({
18888
+ view: this._view,
18889
+ color: this._source.color,
18890
+ height: preview.group.height(),
18891
+ waveformDataFunc: function () {
18892
+ return self._createWaveformDataPointsIterator();
18893
+ }
18894
+ });
18895
+ preview.group.add(waveform);
18896
+ this._addToUnwrap(preview.group);
18897
+ if (redraw) {
18898
+ this._scheduleBatchDraw();
18899
+ }
18900
+ this._previewList.push(preview);
18901
+ };
18902
+ SourceGroup.prototype.updateWaveformDataPreview = function (redraw) {
18903
+ if (!this._hasWaveformData()) {
18904
+ this._removeWaveformDataPreview();
18905
+ return;
18906
+ }
18907
+ var existing = this._previewList.filter(function (preview) {
18908
+ return preview && preview.type === 'waveformData';
18909
+ });
18910
+ if (existing.length === 0) {
18911
+ this.addWaveformDataPreview(redraw);
18912
+ return;
18913
+ }
18914
+ var expectedHeight = this._source.previewUrl ? this._source.binaryHeight : this._unwrappedHeight;
18915
+ existing.forEach(function (preview) {
18916
+ if (preview.group && typeof preview.group.height === 'function') {
18917
+ preview.group.height(expectedHeight);
18918
+ }
18919
+ });
18920
+ if (redraw) {
18921
+ this._scheduleBatchDraw();
18922
+ }
18923
+ };
18836
18924
  SourceGroup.prototype._createAudioPreview = function (preview, redraw) {
18837
18925
  var url = preview.url;
18838
18926
  var scaledData = this._layer.getLoadedData(url + '-scaled');
@@ -18925,6 +19013,131 @@ module.exports = function (WaveformBuilder, WaveformShape, Loader, Invoker, Util
18925
19013
  }
18926
19014
  };
18927
19015
  };
19016
+ SourceGroup.prototype._createWaveformDataPointsIterator = function () {
19017
+ var custom = this._getWaveformData();
19018
+ if (!custom || typeof custom !== 'object') {
19019
+ return {
19020
+ next: function () {
19021
+ return { done: true };
19022
+ }
19023
+ };
19024
+ }
19025
+ var bucketSizeSec = custom.bucketSizeSec;
19026
+ var startTimeSec = custom.startTimeSec;
19027
+ var minRaw = custom.min;
19028
+ var maxRaw = custom.max;
19029
+ if (!Utils.isValidTime(bucketSizeSec) || bucketSizeSec <= 0) {
19030
+ return {
19031
+ next: function () {
19032
+ return { done: true };
19033
+ }
19034
+ };
19035
+ }
19036
+ if (!Utils.isValidTime(startTimeSec)) {
19037
+ startTimeSec = this._source && Utils.isValidTime(this._source.mediaStartTime) ? this._source.mediaStartTime : 0;
19038
+ }
19039
+ var minByChannel;
19040
+ var maxByChannel;
19041
+ var channels = 1;
19042
+ if (Array.isArray(minRaw) && minRaw.length > 0 && Array.isArray(minRaw[0])) {
19043
+ minByChannel = minRaw;
19044
+ channels = minRaw.length;
19045
+ } else {
19046
+ minByChannel = [Array.isArray(minRaw) ? minRaw : []];
19047
+ channels = 1;
19048
+ }
19049
+ if (Array.isArray(maxRaw) && maxRaw.length > 0 && Array.isArray(maxRaw[0])) {
19050
+ maxByChannel = maxRaw;
19051
+ channels = Math.max(channels, maxRaw.length);
19052
+ } else {
19053
+ maxByChannel = [Array.isArray(maxRaw) ? maxRaw : []];
19054
+ }
19055
+ for (var c = 0; c < channels; c++) {
19056
+ if (!minByChannel[c]) {
19057
+ minByChannel[c] = [];
19058
+ }
19059
+ if (!maxByChannel[c]) {
19060
+ maxByChannel[c] = [];
19061
+ }
19062
+ }
19063
+ var view = this._view;
19064
+ var source = this._source;
19065
+ var groupX = this._group && typeof this._group.x === 'function' ? this._group.x() : 0;
19066
+ var groupWidth = this._width;
19067
+ var viewWidth = view.getWidth();
19068
+ var hiddenLeftPixels = Math.max(-groupX, 0);
19069
+ var hiddenRightPixels = Math.max(groupX + groupWidth - viewWidth, 0);
19070
+ var baseMediaStartTime = source && Utils.isValidTime(source.mediaStartTime) ? source.mediaStartTime : startTimeSec;
19071
+ var visibleMediaStartTime = baseMediaStartTime;
19072
+ var visibleMediaEndTime = source && Utils.isValidTime(source.mediaEndTime) ? source.mediaEndTime : baseMediaStartTime + (source.endTime - source.startTime);
19073
+ visibleMediaStartTime += view.pixelsToTime(hiddenLeftPixels);
19074
+ visibleMediaEndTime -= view.pixelsToTime(hiddenRightPixels);
19075
+ if (visibleMediaEndTime < visibleMediaStartTime) {
19076
+ visibleMediaEndTime = visibleMediaStartTime;
19077
+ }
19078
+ var startIndex = Math.max(0, Math.floor((visibleMediaStartTime - startTimeSec) / bucketSizeSec));
19079
+ var endIndex = Math.max(startIndex, Math.ceil((visibleMediaEndTime - startTimeSec) / bucketSizeSec));
19080
+ var maxLen = 0;
19081
+ for (var cc = 0; cc < channels; cc++) {
19082
+ maxLen = Math.max(maxLen, minByChannel[cc].length, maxByChannel[cc].length);
19083
+ }
19084
+ endIndex = Math.min(endIndex, maxLen);
19085
+ function toInt8Amplitude(v) {
19086
+ if (!Utils.isNumber(v) || !Number.isFinite(v)) {
19087
+ return null;
19088
+ }
19089
+ if (v <= -1) {
19090
+ return -128;
19091
+ }
19092
+ if (v >= 1) {
19093
+ return 127;
19094
+ }
19095
+ return Math.round(v * 127);
19096
+ }
19097
+ var startOffsetPx = view.timeToPixels(baseMediaStartTime);
19098
+ var xBucket = startIndex;
19099
+ return {
19100
+ next: function () {
19101
+ while (xBucket < endIndex) {
19102
+ var min = new Array(channels);
19103
+ var max = new Array(channels);
19104
+ var hasAny = false;
19105
+ for (var i = 0; i < channels; i++) {
19106
+ var minV = toInt8Amplitude(minByChannel[i][xBucket]);
19107
+ var maxV = toInt8Amplitude(maxByChannel[i][xBucket]);
19108
+ if (minV === null && maxV !== null) {
19109
+ minV = maxV;
19110
+ }
19111
+ if (maxV === null && minV !== null) {
19112
+ maxV = minV;
19113
+ }
19114
+ if (minV === null && maxV === null) {
19115
+ min[i] = 0;
19116
+ max[i] = 0;
19117
+ } else {
19118
+ min[i] = minV;
19119
+ max[i] = maxV;
19120
+ hasAny = true;
19121
+ }
19122
+ }
19123
+ var bucketTime = startTimeSec + xBucket * bucketSizeSec;
19124
+ var xPx = view.timeToPixels(bucketTime) - startOffsetPx + 0.5;
19125
+ xBucket++;
19126
+ if (hasAny) {
19127
+ return {
19128
+ done: false,
19129
+ value: {
19130
+ x: xPx,
19131
+ min: min,
19132
+ max: max
19133
+ }
19134
+ };
19135
+ }
19136
+ }
19137
+ return { done: true };
19138
+ }
19139
+ };
19140
+ };
18928
19141
  SourceGroup.prototype.getAudioPreview = function () {
18929
19142
  return this._previewList.filter(function (preview) {
18930
19143
  return preview.type === 'audio';
@@ -19719,7 +19932,7 @@ module.exports = function (SourceGroup, LineGroups, DataRetriever, Invoker, Util
19719
19932
  var width = this._view.getWidth();
19720
19933
  this.updateSources(this._view.pixelsToTime(frameOffset), this._view.pixelsToTime(frameOffset + width));
19721
19934
  };
19722
- SourcesLayer.prototype._onSourceUpdate = function (source) {
19935
+ SourcesLayer.prototype._onSourceUpdate = function (source, meta) {
19723
19936
  const sourceGroup = this._sourcesGroup[source.id];
19724
19937
  const frameOffset = this._view.getFrameOffset();
19725
19938
  const width = this._view.getWidth();
@@ -19731,6 +19944,10 @@ module.exports = function (SourceGroup, LineGroups, DataRetriever, Invoker, Util
19731
19944
  var isActiveDraggedSource = false;
19732
19945
  var draggedAbsPos = null;
19733
19946
  var pointerPos = null;
19947
+ if (sourceGroup && meta && meta.onlyWaveformData) {
19948
+ sourceGroup.updateWaveformDataPreview(true);
19949
+ return;
19950
+ }
19734
19951
  if (sourceGroup) {
19735
19952
  isSourceGroupHovered = sourceGroup.isHovered();
19736
19953
  isSourceGroupDragged = sourceGroup.isDragged();
@@ -19857,6 +20074,9 @@ module.exports = function (SourceGroup, LineGroups, DataRetriever, Invoker, Util
19857
20074
  var sourceGroup = this._createSourceGroup(source);
19858
20075
  this._sourcesGroup[source.id] = sourceGroup;
19859
20076
  this._lineGroups.addSource(source, sourceGroup);
20077
+ if (source && source.waveformData) {
20078
+ sourceGroup.addWaveformDataPreview(false);
20079
+ }
19860
20080
  if (startDataRetrieval) {
19861
20081
  this._dataRetriever.retrieveData(source);
19862
20082
  }
@@ -21779,14 +21999,20 @@ module.exports = function (Utils) {
21779
21999
  } else if (!Utils.isNumber(options.binaryHeight)) {
21780
22000
  throw new TypeError('peaks.sources.' + context + ': binaryHeight must be a number');
21781
22001
  }
22002
+ var hasWaveformData = !Utils.isNullOrUndefined(options.waveformData);
22003
+ var hasWaveformArea = Boolean(options.binaryUrl) || hasWaveformData;
21782
22004
  if (options.previewUrl && !options.previewHeight) {
21783
- if (options.binaryHeight) {
21784
- options.previewHeight = Math.max(peaks.options.lineHeight - options.binaryHeight, 0);
22005
+ if (hasWaveformArea) {
22006
+ if (options.binaryHeight) {
22007
+ options.previewHeight = Math.max(peaks.options.lineHeight - options.binaryHeight, 0);
22008
+ } else {
22009
+ options.previewHeight = peaks.options.lineHeight / 2;
22010
+ }
21785
22011
  } else {
21786
- options.previewHeight = peaks.options.lineHeight / 2;
22012
+ options.previewHeight = peaks.options.lineHeight;
21787
22013
  }
21788
22014
  }
21789
- if (options.binaryUrl && !options.binaryHeight) {
22015
+ if (hasWaveformArea && !options.binaryHeight) {
21790
22016
  options.binaryHeight = Math.max(peaks.options.lineHeight - options.previewHeight, 0);
21791
22017
  }
21792
22018
  if (options.wrapping === 'reduced') {
@@ -21836,7 +22062,7 @@ module.exports = function (Utils) {
21836
22062
  throw new TypeError('peaks.sources.' + context + ': targetSpeed must be a positive number');
21837
22063
  }
21838
22064
  }
21839
- function Source(peaks, id, lineId, originId, elementId, title, titleAlignments, url, previewUrl, binaryUrl, kind, subkind, duration, startTime, endTime, mediaStartTime, mediaEndTime, color, backgroundColor, hoverBackgroundColor, selectedBackgroundColor, borderColor, selectedBorderColor, warningColor, warningWidth, volumeSliderColor, volumeSliderWidth, volumeSliderDraggingWidth, textFont, textFontSize, textColor, textBackgroundColor, textPosition, textAutoScroll, borderWidth, borderRadius, wrapped, draggable, orderable, resizable, cuttable, deletable, wrapping, previewHeight, binaryHeight, indicators, markers, buttons, markerColor, markerWidth, volume, volumeRange, loading, targetSpeed, ...customParams) {
22065
+ function Source(peaks, id, lineId, originId, elementId, title, titleAlignments, url, previewUrl, binaryUrl, kind, subkind, duration, startTime, endTime, mediaStartTime, mediaEndTime, color, backgroundColor, hoverBackgroundColor, selectedBackgroundColor, borderColor, selectedBorderColor, warningColor, warningWidth, volumeSliderColor, volumeSliderWidth, volumeSliderDraggingWidth, textFont, textFontSize, textColor, textBackgroundColor, textPosition, textAutoScroll, borderWidth, borderRadius, wrapped, draggable, orderable, resizable, cuttable, deletable, wrapping, previewHeight, binaryHeight, indicators, markers, buttons, markerColor, markerWidth, volume, volumeRange, loading, targetSpeed, waveformData, ...customParams) {
21840
22066
  var opts = {
21841
22067
  title: title,
21842
22068
  titleAlignments: titleAlignments,
@@ -21886,7 +22112,8 @@ module.exports = function (Utils) {
21886
22112
  volume: volume,
21887
22113
  volumeRange: volumeRange,
21888
22114
  loading: loading,
21889
- targetSpeed: targetSpeed
22115
+ targetSpeed: targetSpeed,
22116
+ waveformData: waveformData
21890
22117
  };
21891
22118
  validateSource(peaks, opts, 'add()');
21892
22119
  this._peaks = peaks;
@@ -21943,6 +22170,7 @@ module.exports = function (Utils) {
21943
22170
  this._volumeRange = opts.volumeRange;
21944
22171
  this._loading = opts.loading;
21945
22172
  this._targetSpeed = opts.targetSpeed;
22173
+ this._waveformData = opts.waveformData;
21946
22174
  this._minSize = peaks.options.minSourceSize;
21947
22175
  this._selected = false;
21948
22176
  for (var i = 0; i < customParams.length; i += 2) {
@@ -22014,6 +22242,15 @@ module.exports = function (Utils) {
22014
22242
  return this._binaryUrl;
22015
22243
  }
22016
22244
  },
22245
+ waveformData: {
22246
+ enumerable: true,
22247
+ get: function () {
22248
+ return this._waveformData;
22249
+ },
22250
+ set: function (waveformData) {
22251
+ this._waveformData = waveformData;
22252
+ }
22253
+ },
22017
22254
  kind: {
22018
22255
  enumerable: true,
22019
22256
  get: function () {
@@ -22560,6 +22797,7 @@ module.exports = function (Utils) {
22560
22797
  this._volumeRange = opts.volumeRange;
22561
22798
  this._loading = opts.loading;
22562
22799
  this._targetSpeed = opts.targetSpeed;
22800
+ this._waveformData = opts.waveformData;
22563
22801
  if (options && typeof options === 'object') {
22564
22802
  for (var key in options) {
22565
22803
  if (Object.prototype.hasOwnProperty.call(options, key) && key.startsWith('custom_')) {
@@ -22567,7 +22805,14 @@ module.exports = function (Utils) {
22567
22805
  }
22568
22806
  }
22569
22807
  }
22570
- this._peaks.emit('model.source.update', this);
22808
+ var updatedKeys = [];
22809
+ if (options && typeof options === 'object') {
22810
+ updatedKeys = Object.keys(options);
22811
+ }
22812
+ this._peaks.emit('model.source.update', this, {
22813
+ updatedKeys: updatedKeys,
22814
+ onlyWaveformData: updatedKeys.length === 1 && updatedKeys[0] === 'waveformData'
22815
+ });
22571
22816
  };
22572
22817
  Source.prototype.isVisible = function (startTime, endTime) {
22573
22818
  return this._startTime < endTime && startTime < this._endTime;
@@ -22874,7 +23119,8 @@ module.exports = function (Source, Utils) {
22874
23119
  volume: sourceToCut.volume,
22875
23120
  volumeRange: sourceToCut.volumeRange,
22876
23121
  loading: sourceToCut.loading,
22877
- targetSpeed: sourceToCut.targetSpeed
23122
+ targetSpeed: sourceToCut.targetSpeed,
23123
+ waveformData: sourceToCut.waveformData
22878
23124
  };
22879
23125
  for (var key in sourceToCut) {
22880
23126
  if (key.startsWith('custom_')) {
@@ -22904,7 +23150,7 @@ module.exports = function (Source, Utils) {
22904
23150
  customParams.push(key, value);
22905
23151
  }
22906
23152
  });
22907
- var source = new Source(this._peaks, options.id || this._getNextSourceId(), options.lineId, options.originId, options.elementId, options.title, options.titleAlignments, options.url, options.previewUrl, options.binaryUrl, options.kind, options.subkind, options.duration, options.startTime, options.endTime, options.mediaStartTime, options.mediaEndTime, options.color, options.backgroundColor, options.hoverBackgroundColor, options.selectedBackgroundColor, options.borderColor, options.selectedBorderColor, options.warningColor, options.warningWidth, options.volumeSliderColor, options.volumeSliderWidth, options.volumeSliderDraggingWidth, options.textFont, options.textFontSize, options.textColor, options.textBackgroundColor, options.textPosition, options.textAutoScroll, options.borderWidth, options.borderRadius, options.wrapped, options.draggable, options.orderable, options.resizable, options.cuttable, options.deletable, options.wrapping, options.previewHeight, options.binaryHeight, options.indicators, options.markers, options.buttons, options.markerColor, options.markerWidth, options.volume, options.volumeRange, options.loading, options.targetSpeed, ...customParams);
23153
+ var source = new Source(this._peaks, options.id || this._getNextSourceId(), options.lineId, options.originId, options.elementId, options.title, options.titleAlignments, options.url, options.previewUrl, options.binaryUrl, options.kind, options.subkind, options.duration, options.startTime, options.endTime, options.mediaStartTime, options.mediaEndTime, options.color, options.backgroundColor, options.hoverBackgroundColor, options.selectedBackgroundColor, options.borderColor, options.selectedBorderColor, options.warningColor, options.warningWidth, options.volumeSliderColor, options.volumeSliderWidth, options.volumeSliderDraggingWidth, options.textFont, options.textFontSize, options.textColor, options.textBackgroundColor, options.textPosition, options.textAutoScroll, options.borderWidth, options.borderRadius, options.wrapped, options.draggable, options.orderable, options.resizable, options.cuttable, options.deletable, options.wrapping, options.previewHeight, options.binaryHeight, options.indicators, options.markers, options.buttons, options.markerColor, options.markerWidth, options.volume, options.volumeRange, options.loading, options.targetSpeed, options.waveformData, ...customParams);
22908
23154
  return source;
22909
23155
  };
22910
23156
  SourceHandler.prototype.getSources = function () {
@@ -35,7 +35,11 @@ define([
35
35
 
36
36
  DataRetriever.prototype.retrieveData = function(source) {
37
37
  this._retrieveDataByUrl(source, source.previewUrl);
38
- this._retrieveDataByUrl(source, source.binaryUrl);
38
+ // If waveformData is supplied on the source, prefer it over
39
+ // remote/binary waveform retrieval.
40
+ if (!source.waveformData) {
41
+ this._retrieveDataByUrl(source, source.binaryUrl);
42
+ }
39
43
  };
40
44
 
41
45
  DataRetriever.prototype._retrieveDataByUrl = function(source, url) {
@@ -980,6 +980,119 @@ define([
980
980
  }
981
981
  };
982
982
 
983
+ SourceGroup.prototype._getWaveformData = function() {
984
+ return this._source && this._source.waveformData ? this._source.waveformData : null;
985
+ };
986
+
987
+ SourceGroup.prototype._hasWaveformData = function() {
988
+ return Boolean(this._getWaveformData());
989
+ };
990
+
991
+ SourceGroup.prototype._removeWaveformDataPreview = function() {
992
+ if (!this._previewList || this._previewList.length === 0) {
993
+ return;
994
+ }
995
+
996
+ var removed = false;
997
+
998
+ this._previewList = this._previewList.filter(function(preview) {
999
+ if (preview && preview.type === 'waveformData') {
1000
+ if (preview.group && typeof preview.group.destroy === 'function') {
1001
+ preview.group.destroy();
1002
+ }
1003
+ removed = true;
1004
+ return false;
1005
+ }
1006
+ return true;
1007
+ });
1008
+
1009
+ if (removed) {
1010
+ this._scheduleBatchDraw();
1011
+ }
1012
+ };
1013
+
1014
+ SourceGroup.prototype.addWaveformDataPreview = function(redraw) {
1015
+ if (!this._hasWaveformData()) {
1016
+ return;
1017
+ }
1018
+
1019
+ // If the source starts wrapped, _unwrap may not exist yet. We still build
1020
+ // previews into the unwrap container so they appear when the source is
1021
+ // expanded later.
1022
+ if (!this._unwrap) {
1023
+ this._unwrap = this._createUnwrap();
1024
+ }
1025
+
1026
+ // Avoid duplicates.
1027
+ var existing = this._previewList.filter(function(preview) {
1028
+ return preview && preview.type === 'waveformData';
1029
+ });
1030
+
1031
+ if (existing.length > 0) {
1032
+ if (redraw) {
1033
+ this._scheduleBatchDraw();
1034
+ }
1035
+ return;
1036
+ }
1037
+
1038
+ var preview = {
1039
+ type: 'waveformData',
1040
+ group: new Konva.Group({
1041
+ height: (this._source.previewUrl ? this._source.binaryHeight : this._unwrappedHeight),
1042
+ listening: false
1043
+ })
1044
+ };
1045
+
1046
+ var self = this;
1047
+
1048
+ var waveform = new WaveformShape({
1049
+ view: this._view,
1050
+ color: this._source.color,
1051
+ height: preview.group.height(),
1052
+ waveformDataFunc: function() {
1053
+ return self._createWaveformDataPointsIterator();
1054
+ }
1055
+ });
1056
+
1057
+ preview.group.add(waveform);
1058
+ this._addToUnwrap(preview.group);
1059
+
1060
+ if (redraw) {
1061
+ this._scheduleBatchDraw();
1062
+ }
1063
+
1064
+ this._previewList.push(preview);
1065
+ };
1066
+
1067
+ SourceGroup.prototype.updateWaveformDataPreview = function(redraw) {
1068
+ if (!this._hasWaveformData()) {
1069
+ this._removeWaveformDataPreview();
1070
+ return;
1071
+ }
1072
+
1073
+ var existing = this._previewList.filter(function(preview) {
1074
+ return preview && preview.type === 'waveformData';
1075
+ });
1076
+
1077
+ if (existing.length === 0) {
1078
+ this.addWaveformDataPreview(redraw);
1079
+ return;
1080
+ }
1081
+
1082
+ // Height may depend on previewUrl presence.
1083
+ var expectedHeight = (this._source.previewUrl ? this._source.binaryHeight : this._unwrappedHeight);
1084
+
1085
+ existing.forEach(function(preview) {
1086
+ if (preview.group && typeof preview.group.height === 'function') {
1087
+ preview.group.height(expectedHeight);
1088
+ }
1089
+ });
1090
+
1091
+ if (redraw) {
1092
+ this._scheduleBatchDraw();
1093
+ }
1094
+ };
1095
+
983
1096
  SourceGroup.prototype._createAudioPreview = function(preview, redraw) {
984
1097
  var url = preview.url;
985
1098
 
@@ -1116,6 +1229,178 @@ define([
1116
1229
  };
1117
1230
  };
1118
1231
 
1232
+ SourceGroup.prototype._createWaveformDataPointsIterator = function() {
1233
+ var custom = this._getWaveformData();
1234
+
1235
+ if (!custom || typeof custom !== 'object') {
1236
+ return {
1237
+ next: function() {
1238
+ return { done: true };
1239
+ }
1240
+ };
1241
+ }
1242
+
1243
+ var bucketSizeSec = custom.bucketSizeSec;
1244
+ var startTimeSec = custom.startTimeSec;
1245
+ var minRaw = custom.min;
1246
+ var maxRaw = custom.max;
1247
+
1248
+ if (!Utils.isValidTime(bucketSizeSec) || bucketSizeSec <= 0) {
1249
+ return {
1250
+ next: function() {
1251
+ return { done: true };
1252
+ }
1253
+ };
1254
+ }
1255
+
1256
+ if (!Utils.isValidTime(startTimeSec)) {
1257
+ // Fallback to current model mediaStartTime if missing.
1258
+ startTimeSec = this._source && Utils.isValidTime(this._source.mediaStartTime) ? this._source.mediaStartTime : 0;
1259
+ }
1260
+
1261
+ // Normalize to channel arrays.
1262
+ var minByChannel;
1263
+ var maxByChannel;
1264
+ var channels = 1;
1265
+
1266
+ if (Array.isArray(minRaw) && minRaw.length > 0 && Array.isArray(minRaw[0])) {
1267
+ minByChannel = minRaw;
1268
+ channels = minRaw.length;
1269
+ }
1270
+ else {
1271
+ minByChannel = [Array.isArray(minRaw) ? minRaw : []];
1272
+ channels = 1;
1273
+ }
1274
+
1275
+ if (Array.isArray(maxRaw) && maxRaw.length > 0 && Array.isArray(maxRaw[0])) {
1276
+ maxByChannel = maxRaw;
1277
+ channels = Math.max(channels, maxRaw.length);
1278
+ }
1279
+ else {
1280
+ maxByChannel = [Array.isArray(maxRaw) ? maxRaw : []];
1281
+ }
1282
+
1283
+ // Ensure arrays exist for all channels.
1284
+ for (var c = 0; c < channels; c++) {
1285
+ if (!minByChannel[c]) {
1286
+ minByChannel[c] = [];
1287
+ }
1288
+ if (!maxByChannel[c]) {
1289
+ maxByChannel[c] = [];
1290
+ }
1291
+ }
1292
+
1293
+ // Determine visible time range for this source, including drag offsets.
1294
+ var view = this._view;
1295
+ var source = this._source;
1296
+ var groupX = this._group && typeof this._group.x === 'function' ? this._group.x() : 0;
1297
+ var groupWidth = this._width;
1298
+ var viewWidth = view.getWidth();
1299
+
1300
+ var hiddenLeftPixels = Math.max(-groupX, 0);
1301
+ var hiddenRightPixels = Math.max(groupX + groupWidth - viewWidth, 0);
1302
+
1303
+ // Base media start time for x-axis alignment.
1304
+ var baseMediaStartTime = (source && Utils.isValidTime(source.mediaStartTime))
1305
+ ? source.mediaStartTime
1306
+ : startTimeSec;
1307
+
1308
+ // Translate pixel visibility into time range in the media timeline.
1309
+ var visibleMediaStartTime = baseMediaStartTime;
1310
+ var visibleMediaEndTime = (source && Utils.isValidTime(source.mediaEndTime))
1311
+ ? source.mediaEndTime
1312
+ : (baseMediaStartTime + (source.endTime - source.startTime));
1313
+
1314
+ // Account for the portion hidden outside the viewport (during dragging/scrolling).
1315
+ visibleMediaStartTime += view.pixelsToTime(hiddenLeftPixels);
1316
+ visibleMediaEndTime -= view.pixelsToTime(hiddenRightPixels);
1317
+
1318
+ if (visibleMediaEndTime < visibleMediaStartTime) {
1319
+ visibleMediaEndTime = visibleMediaStartTime;
1320
+ }
1321
+
1322
+ // Map time range to bucket indices.
1323
+ var startIndex = Math.max(0, Math.floor((visibleMediaStartTime - startTimeSec) / bucketSizeSec));
1324
+ var endIndex = Math.max(startIndex, Math.ceil((visibleMediaEndTime - startTimeSec) / bucketSizeSec));
1325
+
1326
+ // Clamp endIndex to available data.
1327
+ var maxLen = 0;
1328
+
1329
+ for (var cc = 0; cc < channels; cc++) {
1330
+ maxLen = Math.max(maxLen, minByChannel[cc].length, maxByChannel[cc].length);
1331
+ }
1332
+ endIndex = Math.min(endIndex, maxLen);
1333
+
1334
+ // Convert float amplitudes (-1..1) to signed 8-bit-ish range (-128..127).
1335
+ function toInt8Amplitude(v) {
1336
+ if (!Utils.isNumber(v) || !Number.isFinite(v)) {
1337
+ return null;
1338
+ }
1339
+ if (v <= -1) {
1340
+ return -128;
1341
+ }
1342
+ if (v >= 1) {
1343
+ return 127;
1344
+ }
1345
+ // Use 127 as positive peak to match common signed 8-bit scaling.
1346
+ return Math.round(v * 127);
1347
+ }
1348
+
1349
+ var startOffsetPx = view.timeToPixels(baseMediaStartTime);
1350
+ var xBucket = startIndex;
1351
+
1352
+ return {
1353
+ next: function() {
1354
+ while (xBucket < endIndex) {
1355
+ var min = new Array(channels);
1356
+ var max = new Array(channels);
1357
+ var hasAny = false;
1358
+
1359
+ for (var i = 0; i < channels; i++) {
1360
+ var minV = toInt8Amplitude(minByChannel[i][xBucket]);
1361
+ var maxV = toInt8Amplitude(maxByChannel[i][xBucket]);
1362
+
1363
+ // Draw what we have: if one side is missing, fall back to the other.
1364
+ if (minV === null && maxV !== null) {
1365
+ minV = maxV;
1366
+ }
1367
+ if (maxV === null && minV !== null) {
1368
+ maxV = minV;
1369
+ }
1370
+
1371
+ if (minV === null && maxV === null) {
1372
+ min[i] = 0;
1373
+ max[i] = 0;
1374
+ }
1375
+ else {
1376
+ min[i] = minV;
1377
+ max[i] = maxV;
1378
+ hasAny = true;
1379
+ }
1380
+ }
1381
+
1382
+ var bucketTime = startTimeSec + xBucket * bucketSizeSec;
1383
+ var xPx = view.timeToPixels(bucketTime) - startOffsetPx + 0.5;
1384
+
1385
+ xBucket++;
1386
+
1387
+ if (hasAny) {
1388
+ return {
1389
+ done: false,
1390
+ value: {
1391
+ x: xPx,
1392
+ min: min,
1393
+ max: max
1394
+ }
1395
+ };
1396
+ }
1397
+ }
1398
+
1399
+ return { done: true };
1400
+ }
1401
+ };
1402
+ };
1403
+
1119
1404
  SourceGroup.prototype.getAudioPreview = function() {
1120
1405
  return this._previewList.filter(function(preview) {
1121
1406
  return preview.type === 'audio';
@@ -151,7 +151,7 @@ define([
151
151
  );
152
152
  };
153
153
 
154
- SourcesLayer.prototype._onSourceUpdate = function(source) {
154
+ SourcesLayer.prototype._onSourceUpdate = function(source, meta) {
155
155
  const sourceGroup = this._sourcesGroup[source.id];
156
156
  const frameOffset = this._view.getFrameOffset();
157
157
  const width = this._view.getWidth();
@@ -168,6 +168,13 @@ define([
168
168
  var draggedAbsPos = null;
169
169
  var pointerPos = null;
170
170
 
171
+ // Fast-path: if only the custom waveform changed, update the existing
172
+ // preview in-place and redraw without rebuilding the entire group.
173
+ if (sourceGroup && meta && meta.onlyWaveformData) {
174
+ sourceGroup.updateWaveformDataPreview(true);
175
+ return;
176
+ }
177
+
171
178
  if (sourceGroup) {
172
179
  isSourceGroupHovered = sourceGroup.isHovered();
173
180
  isSourceGroupDragged = sourceGroup.isDragged();
@@ -366,6 +373,12 @@ define([
366
373
  this._sourcesGroup[source.id] = sourceGroup;
367
374
  this._lineGroups.addSource(source, sourceGroup);
368
375
 
376
+ // If a custom waveform is provided on the source, render it immediately.
377
+ // This is local data, so no network retrieval is needed for the waveform.
378
+ if (source && source.waveformData) {
379
+ sourceGroup.addWaveformDataPreview(false);
380
+ }
381
+
369
382
  // After creating and referencing the new group, we can start data retrieval
370
383
  if (startDataRetrieval) {
371
384
  this._dataRetriever.retrieveData(source);
@@ -298,16 +298,26 @@ define([
298
298
  throw new TypeError('peaks.sources.' + context + ': binaryHeight must be a number');
299
299
  }
300
300
 
301
+ // Treat waveformData as a waveform source, similar to binaryUrl.
302
+ var hasWaveformData = !Utils.isNullOrUndefined(options.waveformData);
303
+ var hasWaveformArea = Boolean(options.binaryUrl) || hasWaveformData;
304
+
301
305
  if (options.previewUrl && !options.previewHeight) {
302
- if (options.binaryHeight) {
303
- options.previewHeight = Math.max(peaks.options.lineHeight - options.binaryHeight, 0);
306
+ if (hasWaveformArea) {
307
+ if (options.binaryHeight) {
308
+ options.previewHeight = Math.max(peaks.options.lineHeight - options.binaryHeight, 0);
309
+ }
310
+ else {
311
+ options.previewHeight = peaks.options.lineHeight / 2;
312
+ }
304
313
  }
305
314
  else {
306
- options.previewHeight = peaks.options.lineHeight / 2;
315
+ // If only a preview is shown, default it to the full available height.
316
+ options.previewHeight = peaks.options.lineHeight;
307
317
  }
308
318
  }
309
319
 
310
- if (options.binaryUrl && !options.binaryHeight) {
320
+ if (hasWaveformArea && !options.binaryHeight) {
311
321
  options.binaryHeight = Math.max(peaks.options.lineHeight - options.previewHeight, 0);
312
322
  }
313
323
 
@@ -444,7 +454,7 @@ define([
444
454
  volumeSliderWidth, volumeSliderDraggingWidth, textFont, textFontSize, textColor, textBackgroundColor, textPosition,
445
455
  textAutoScroll, borderWidth, borderRadius, wrapped, draggable, orderable, resizable,
446
456
  cuttable, deletable, wrapping, previewHeight, binaryHeight, indicators, markers, buttons, markerColor,
447
- markerWidth, volume, volumeRange, loading, targetSpeed, ...customParams) {
457
+ markerWidth, volume, volumeRange, loading, targetSpeed, waveformData, ...customParams) {
448
458
  var opts = {
449
459
  title: title,
450
460
  titleAlignments: titleAlignments,
@@ -494,7 +504,8 @@ define([
494
504
  volume: volume,
495
505
  volumeRange: volumeRange,
496
506
  loading: loading,
497
- targetSpeed: targetSpeed
507
+ targetSpeed: targetSpeed,
508
+ waveformData: waveformData
498
509
  };
499
510
 
500
511
  validateSource(peaks, opts, 'add()');
@@ -553,6 +564,7 @@ define([
553
564
  this._volumeRange = opts.volumeRange;
554
565
  this._loading = opts.loading;
555
566
  this._targetSpeed = opts.targetSpeed;
567
+ this._waveformData = opts.waveformData;
556
568
  this._minSize = peaks.options.minSourceSize;
557
569
  this._selected = false;
558
570
 
@@ -629,6 +641,16 @@ define([
629
641
  return this._binaryUrl;
630
642
  }
631
643
  },
644
+ waveformData: {
645
+ enumerable: true,
646
+ get: function() {
647
+ return this._waveformData;
648
+ },
649
+
650
+ set: function(waveformData) {
651
+ this._waveformData = waveformData;
652
+ }
653
+ },
632
654
  kind: {
633
655
  enumerable: true,
634
656
  get: function() {
@@ -1236,6 +1258,7 @@ define([
1236
1258
  this._volumeRange = opts.volumeRange;
1237
1259
  this._loading = opts.loading;
1238
1260
  this._targetSpeed = opts.targetSpeed;
1261
+ this._waveformData = opts.waveformData;
1239
1262
 
1240
1263
  if (options && typeof options === 'object') {
1241
1264
  for (var key in options) {
@@ -1245,7 +1268,16 @@ define([
1245
1268
  }
1246
1269
  }
1247
1270
 
1248
- this._peaks.emit('model.source.update', this);
1271
+ var updatedKeys = [];
1272
+
1273
+ if (options && typeof options === 'object') {
1274
+ updatedKeys = Object.keys(options);
1275
+ }
1276
+
1277
+ this._peaks.emit('model.source.update', this, {
1278
+ updatedKeys: updatedKeys,
1279
+ onlyWaveformData: updatedKeys.length === 1 && updatedKeys[0] === 'waveformData'
1280
+ });
1249
1281
  };
1250
1282
 
1251
1283
  /**
@@ -110,7 +110,8 @@ define([
110
110
  volume: sourceToCut.volume,
111
111
  volumeRange: sourceToCut.volumeRange,
112
112
  loading: sourceToCut.loading,
113
- targetSpeed: sourceToCut.targetSpeed
113
+ targetSpeed: sourceToCut.targetSpeed,
114
+ waveformData: sourceToCut.waveformData
114
115
  };
115
116
 
116
117
  for (var key in sourceToCut) {
@@ -225,6 +226,7 @@ define([
225
226
  options.volumeRange,
226
227
  options.loading,
227
228
  options.targetSpeed,
229
+ options.waveformData,
228
230
  ...customParams
229
231
  );
230
232