• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ossia / score / 30161608849

25 Jul 2026 02:25PM UTC coverage: 15.303% (-0.008%) from 15.311%
30161608849

Pull #2148

github

web-flow
Merge d2b82a62f into 13afd939a
Pull Request #2148: wasm: stream large media from Blob URLs instead of copying into RAM (Tier B)

4 of 179 new or added lines in 10 files covered. (2.23%)

7 existing lines in 5 files now uncovered.

30396 of 198627 relevant lines covered (15.3%)

983.52 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/plugins/score-plugin-gfx/Gfx/Video/View.cpp
1
#include "View.hpp"
2

3
#include <Process/Style/ScenarioStyle.hpp>
4

5
#include <Gfx/Video/Process.hpp>
6
#include <Video/Thumbnailer.hpp>
7

8
#include <score/graphics/GraphicsItem.hpp>
9
#include <score/tools/Bind.hpp>
10
#include <score/tools/ThreadPool.hpp>
11
#include <score/tools/std/Invoke.hpp>
12

13
#include <ossia/detail/closest_element.hpp>
14
#include <ossia/detail/flicks.hpp>
15

16
#include <QGraphicsView>
17
#include <QPainter>
18

19
namespace Gfx::Video
20
{
21

22
View::View(const Model& model, QGraphicsItem* parent)
×
23
    : LayerView{parent}
×
24
    , m_model{model}
×
25
{
×
26
  this->setAcceptDrops(true);
×
27
  this->setAcceptedMouseButtons(Qt::NoButton);
×
28
  setFlag(ItemClipsToShape, true);
×
29
  con(model, &Model::pathChanged, this,
×
30
      [this, &model] { onPathChanged(model.absolutePath()); });
×
31
  con(model, &Model::startOffsetChanged, this, [this](auto) {
×
32
    m_images.clear();
×
33
    widthChanged(width());
×
34
  });
×
35
  con(model, &Model::loopDurationChanged, this, [this](auto) {
×
36
    m_images.clear();
×
37
    widthChanged(width());
×
38
  });
×
39
  con(model, &Model::loopsChanged, this, [this](auto) {
×
40
    m_images.clear();
×
41
    widthChanged(width());
×
42
  });
×
43
  onPathChanged(model.absolutePath());
×
44
}
×
45

46
View::~View()
×
47
{
×
48
  if(m_thumb)
×
49
  {
50
    ossia::qt::run_async(m_thumb, &QObject::deleteLater);
×
51

52
    score::ThreadPool::instance().releaseThread();
×
53
  }
×
54
}
×
55

56
void View::setZoom(ZoomRatio r)
×
57
{
58
  if(r <= 1000)
×
59
    return;
×
60
  m_zoom = r;
×
61
  widthChanged(width());
×
62
  update();
×
63
}
×
64

65
void View::onPathChanged(const QString& str)
×
66
{
67
  auto& inst = score::ThreadPool::instance();
×
68
  QThread* oldThread{};
×
69

70
  if(m_thumb)
×
71
  {
72
    disconnect(m_thumb, &::Video::VideoThumbnailer::thumbnailReady, this, nullptr);
×
73
    oldThread = m_thumb->thread();
×
74
    m_thumb->deleteLater();
×
75
  }
×
76

77
  m_images.clear();
×
NEW
78
  m_thumb = nullptr;
×
79

80
#if defined(__EMSCRIPTEN__)
81
  // A weblocalfile: source is a browser Blob whose JS handle is bound to the
82
  // main thread; the thumbnailer decodes on a worker thread, where reading it
83
  // is undefined behaviour. Skip thumbnails for such (typically very large,
84
  // streamed) sources rather than re-scanning them off-thread.
85
  if(str.startsWith(QLatin1String("weblocalfile:")))
86
    return;
87
#endif
88

89
  m_thumb = new ::Video::VideoThumbnailer{str};
×
90
  if(oldThread)
×
91
    m_thumb->moveToThread(oldThread);
×
92
  else
93
    m_thumb->moveToThread(inst.acquireThread());
×
94

95
  connect(
×
96
      m_thumb, &::Video::VideoThumbnailer::thumbnailReady, this,
×
97
      [this](const int64_t req, const int64_t flicks, QImage img) {
×
98
    if(req == m_lastRequestIndex)
×
99
    {
100
      if(m_images.size() > 50)
×
101
      {
102
        auto it = m_images.upper_bound(flicks);
×
103
        if(it != m_images.end())
×
104
          m_images.erase(it);
×
105
      }
×
106
      m_images[flicks] = std::move(img);
×
107
    }
×
108
    update();
×
109
      },
×
110
      Qt::QueuedConnection);
111

112
  widthChanged(width());
×
113
}
×
114

115
void View::widthChanged(qreal w)
×
116
{
117
  if(w < 10 || !m_thumb)
×
118
    return;
×
119

120
  // TODO we also have to fetch new frames if we scroll !
121
  const double frame_width = m_thumb->smallWidth;
×
122
  if(frame_width < 1.)
×
123
    return;
×
124

125
  auto view = ::getView(*this);
×
126
  if(!view)
×
127
    return;
×
128
  QPointF sceneDrawableTopLeft = view->mapToScene(-10, 0);
×
129
  QPointF sceneDrawableBottomRight
130
      = view->mapToScene(view->width() + 10, view->height() + 10);
×
131
  double itemDrawableLeft = this->mapFromScene(sceneDrawableTopLeft).x();
×
132
  double itemDrawableRight = this->mapFromScene(sceneDrawableBottomRight).x();
×
133

134
  const int count = (itemDrawableRight - itemDrawableLeft) / frame_width + 2;
×
135
  const int start = itemDrawableLeft / frame_width;
×
136

137
  const double flicks_advance = TimeVal::fromPixels(frame_width, m_zoom).impl;
×
138
  if(flicks_advance < ossia::flicks_per_millisecond<double>)
×
139
    return;
×
140

141
  const int64_t startOff = m_model.startOffset().impl;
×
142
  const int64_t loopDur = m_model.loopDuration().impl;
×
143
  const bool loops = m_model.loops() && loopDur > 0;
×
144

145
  QVector<int64_t> v;
×
146
  for(int i = 0; i < count; i++)
×
147
  {
148
    int64_t flicks = (i + start) * flicks_advance;
×
149
    if(loops)
×
150
      flicks = startOff + (flicks % loopDur);
×
151
    else
152
      flicks = startOff + flicks;
×
153
    v.push_back(flicks);
×
154
  }
×
155

156
  m_lastRequestIndex++;
×
157
  m_thumb->requestThumbnails(m_lastRequestIndex, std::move(v));
×
158
  update();
×
159
}
×
160

161
void View::paint_impl(QPainter* painter) const
×
162
{
163
  if(m_images.empty())
×
164
    return;
×
165

166
  auto view = ::getView(*this);
×
167
  if(!view)
×
168
    return;
×
169
  QPointF sceneDrawableTopLeft = view->mapToScene(-10, 0);
×
170
  QPointF sceneDrawableBottomRight
171
      = view->mapToScene(view->width() + 10, view->height() + 10);
×
172
  double itemDrawableLeft = this->mapFromScene(sceneDrawableTopLeft).x();
×
173
  double itemDrawableRight = this->mapFromScene(sceneDrawableBottomRight).x();
×
174

175
  const double frame_width = m_thumb->smallWidth;
×
176
  const int count = (itemDrawableRight - itemDrawableLeft) / frame_width + 2;
×
177
  const int start = itemDrawableLeft / frame_width;
×
178
  const double flicks_advance = TimeVal::fromPixels(frame_width, m_zoom).impl;
×
179
  if(flicks_advance < ossia::flicks_per_millisecond<double>)
×
180
    return;
×
181

182
  const int64_t startOff = m_model.startOffset().impl;
×
183
  const int64_t loopDur = m_model.loopDuration().impl;
×
184
  const bool loops = m_model.loops() && loopDur > 0;
×
185

186
  auto& images = m_images;
×
187

188
  auto it = images.cbegin();
×
189

190
  painter->setRenderHint(QPainter::SmoothPixmapTransform, false);
×
191
  for(int i = 0; i < count; i++)
×
192
  {
193
    const int64_t rawFlicks = (start + i) * flicks_advance;
×
194
    int64_t flicks;
195
    if(loops)
×
196
    {
197
      flicks = startOff + (rawFlicks % loopDur);
×
198
      // Looped flick values are non-monotonic, restart search from beginning
199
      it = ossia::closest_next_element(images.cbegin(), images.cend(), flicks);
×
200
    }
×
201
    else
202
    {
203
      flicks = startOff + rawFlicks;
×
204
      it = ossia::closest_next_element(it, images.cend(), flicks);
×
205
    }
206
    if(it != images.end())
×
207
    {
208
      const double px = TimeVal{rawFlicks}.toPixels(m_zoom);
×
209
      painter->drawImage(QPointF{px, 0.f}, it->second);
×
210
    }
×
211
  }
×
212
}
×
213

214
void View::dropEvent(QGraphicsSceneDragDropEvent* event)
×
215
{
216
  dropReceived(event->pos(), *event->mimeData());
×
217
  event->accept();
×
218
}
×
219
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc