Skip to content

Commit

Permalink
Improved protocol selection & dash fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
rpoleo committed Apr 26, 2021
1 parent 5006c6f commit 563c8a7
Show file tree
Hide file tree
Showing 21 changed files with 188 additions and 39 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ In future releases, the plugin will be named "Video Streaming Plugin" instead of
- The encoder creates a Master Playlist File with the URLs of each Media Playlist.
To play, the client first downloads the Master Playlist, and then the Media Playlists. Then, they play each Media Segment declared within the chosen Media Playlist. The client can reload the Playlist to discover any added segments. This is needed in cases of live events, for example.

Notice that the recognition of the HLS protocol is based on the requirement of the URL extension of the Master playlist link, which must have ".m3u8" on it, as specified on the [ISO regulation](https://tools.ietf.org/html/rfc8216#section-4).
Notice that the automatic recognition of the HLS protocol is based on the requirement of the URL extension of the Master playlist link, which must have ".m3u8" on it, as specified on the [ISO regulation](https://tools.ietf.org/html/rfc8216#section-4).

#### In a Dynamic Adaptive Streaming over HTTP Live Streaming process:

Expand Down Expand Up @@ -61,6 +61,12 @@ Set the link to the master playlist file

![](docs/video-url.png)

#### Protocol

Set the protocol you want to test or let the plugin to automatically detect it.

![](docs/protocol.png)

#### Duration

Set the playback time to either the whole video, or a certain amount of seconds.
Expand Down
Binary file modified docs/audio-and-subtitles.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/bandwidth.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/duration.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/protocol.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/resolution.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/resume-video.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/sampler.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/video-url.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>com.blazemeter.jmeter</groupId>
<artifactId>jmeter-bzm-hls</artifactId>
<version>3.0.3</version>
<version>3.1</version>
<name>Video Streaming Sampler as JMeter plugin</name>

<properties>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.blazemeter.jmeter.hls.logic.BandwidthSelector;
import com.blazemeter.jmeter.hls.logic.HlsSampler;
import com.blazemeter.jmeter.hls.logic.ResolutionSelector;
import com.blazemeter.jmeter.videostreaming.core.Protocol;
import com.google.common.annotations.VisibleForTesting;
import java.awt.BorderLayout;
import org.apache.jmeter.samplers.gui.AbstractSamplerGui;
Expand Down Expand Up @@ -54,6 +55,7 @@ public void configure(TestElement el) {
hlsSamplerPanel.setBandwidthSelector(sampler.getBandwidthSelector());
hlsSamplerPanel.setResolutionSelector(sampler.getResolutionSelector());
hlsSamplerPanel.setResumeStatus(sampler.getResumeVideoStatus());
hlsSamplerPanel.setProtocolSelector(sampler.getProtocolSelector());
}

@Override
Expand All @@ -69,6 +71,7 @@ public void modifyTestElement(TestElement s) {
sampler.setBandwidthSelector(hlsSamplerPanel.getBandwidthSelector());
sampler.setResolutionSelector(hlsSamplerPanel.getResolutionSelector());
sampler.setResumeVideoStatus(hlsSamplerPanel.getResumeVideoStatus());
sampler.setProtocolSelector(hlsSamplerPanel.getProtocolSelector());
}
}

Expand All @@ -83,6 +86,7 @@ public void clearGui() {
hlsSamplerPanel.setBandwidthSelector(BandwidthSelector.MIN);
hlsSamplerPanel.setResolutionSelector(ResolutionSelector.MIN);
hlsSamplerPanel.setResumeStatus(false);
hlsSamplerPanel.setProtocolSelector(Protocol.AUTOMATIC);
}

}
67 changes: 64 additions & 3 deletions src/main/java/com/blazemeter/jmeter/hls/gui/HlsSamplerPanel.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.blazemeter.jmeter.hls.logic.BandwidthSelector;
import com.blazemeter.jmeter.hls.logic.ResolutionSelector;
import com.blazemeter.jmeter.videostreaming.core.Protocol;
import java.awt.event.ItemEvent;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
Expand Down Expand Up @@ -31,8 +32,12 @@ public class HlsSamplerPanel extends JPanel {

private JRadioButton customResolutionOption;
private JTextField customResolutionField;
private JRadioButton maxResolutionOption;
private JRadioButton minResolutionOption;
private JRadioButton maxResolutionOption;

private JRadioButton hlsProtocolOption;
private JRadioButton mpegDashProtocolOption;
private JRadioButton automaticProtocolOption;

private JCheckBox resumeDownloadOption;

Expand All @@ -47,14 +52,18 @@ private void initComponents() {
JPanel bandwidthPanel = buildBandwidthPanel();
JPanel resolutionPanel = buildResolutionPanel();
JPanel resumeDownloadPanel = buildResumeDownloadPanel();
JPanel protocolSelectionPanel = buildProtocolSelectionPanel();

BlazeMeterLabsLogo blazeMeterLabsLogo = new BlazeMeterLabsLogo();
GroupLayout layout = new GroupLayout(this);
layout.setAutoCreateContainerGaps(true);
layout.setAutoCreateGaps(true);
setLayout(layout);
layout.setHorizontalGroup(layout.createParallelGroup()
.addComponent(urlPanel)
.addComponent(urlPanel, GroupLayout.PREFERRED_SIZE,
GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE)
.addComponent(protocolSelectionPanel, GroupLayout.PREFERRED_SIZE,
GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE)
.addGroup(layout.createSequentialGroup()
.addComponent(durationPanel)
.addComponent(trackPanel))
Expand All @@ -68,6 +77,8 @@ private void initComponents() {
layout.setVerticalGroup(layout.createSequentialGroup()
.addComponent(urlPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE,
GroupLayout.PREFERRED_SIZE)
.addComponent(protocolSelectionPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE,
GroupLayout.PREFERRED_SIZE)
.addGroup(layout.createParallelGroup()
.addComponent(durationPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE,
GroupLayout.PREFERRED_SIZE)
Expand All @@ -88,7 +99,7 @@ private JPanel buildUrlPanel() {
panel.setBorder(BorderFactory.createTitledBorder("Video"));

JLabel urlLabel = new JLabel("URL");
masterUrlField = namedComponent("masterUrlField", new JTextField());
masterUrlField = namedComponent("masterUrlField", new JTextField(80));

GroupLayout layout = new GroupLayout(panel);
layout.setAutoCreateContainerGaps(true);
Expand Down Expand Up @@ -295,6 +306,36 @@ private JPanel buildResumeDownloadPanel() {
return panel;
}

private JPanel buildProtocolSelectionPanel() {
JPanel panel = new JPanel();
panel.setBorder(BorderFactory.createTitledBorder("Protocol"));

automaticProtocolOption = namedComponent("automaticProtocolOption",
new JRadioButton(Protocol.AUTOMATIC.toString()));
hlsProtocolOption = namedComponent("hlsProtocolOption",
new JRadioButton(Protocol.HLS.toString()));
mpegDashProtocolOption = namedComponent("mpegDashProtocolOption",
new JRadioButton(Protocol.MPEG_DASH.toString()));

ButtonGroup protocolRadiosGroup = new ButtonGroup();
protocolRadiosGroup.add(automaticProtocolOption);
protocolRadiosGroup.add(hlsProtocolOption);
protocolRadiosGroup.add(mpegDashProtocolOption);

GroupLayout layout = new GroupLayout(panel);
layout.setAutoCreateContainerGaps(true);
panel.setLayout(layout);
layout.setHorizontalGroup(layout.createSequentialGroup()
.addComponent(automaticProtocolOption)
.addComponent(hlsProtocolOption)
.addComponent(mpegDashProtocolOption));
layout.setVerticalGroup(layout.createParallelGroup()
.addComponent(automaticProtocolOption)
.addComponent(hlsProtocolOption)
.addComponent(mpegDashProtocolOption));
return panel;
}

public void setMasterUrl(String masterUrl) {
masterUrlField.setText(masterUrl);
}
Expand Down Expand Up @@ -377,6 +418,26 @@ public void setResolutionSelector(ResolutionSelector option) {
}
}

public Protocol getProtocolSelector() {
if (mpegDashProtocolOption.isSelected()) {
return Protocol.MPEG_DASH;
} else if (hlsProtocolOption.isSelected()) {
return Protocol.HLS;
} else {
return Protocol.AUTOMATIC;
}
}

public void setProtocolSelector(Protocol option) {
if (option == Protocol.AUTOMATIC) {
automaticProtocolOption.setSelected(true);
} else if (option == Protocol.HLS) {
hlsProtocolOption.setSelected(true);
} else {
mpegDashProtocolOption.setSelected(true);
}
}

public boolean getResumeVideoStatus() {
return resumeDownloadOption.isSelected();
}
Expand Down
18 changes: 16 additions & 2 deletions src/main/java/com/blazemeter/jmeter/hls/logic/HlsSampler.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.blazemeter.jmeter.hls.logic;

import com.blazemeter.jmeter.videostreaming.core.Protocol;
import com.blazemeter.jmeter.videostreaming.core.SampleResultProcessor;
import com.blazemeter.jmeter.videostreaming.core.TimeMachine;
import com.blazemeter.jmeter.videostreaming.core.VideoStreamingHttpClient;
Expand Down Expand Up @@ -40,6 +41,7 @@ public class HlsSampler extends HTTPSamplerBase implements Interruptible {
private static final String BANDWIDTH_TYPE_PROPERTY_NAME = "HLS.BANDWIDTH_TYPE";
private static final String RESOLUTION_TYPE_PROPERTY_NAME = "HLS.RESOLUTION_TYPE";
private static final String RESUME_DOWNLOAD_PROPERTY_NAME = "HLS.RESUME_DOWNLOAD";
private static final String PROTOCOL_PROPERTY_NAME = "VIDEO_STREAMING.PROTOCOL";

private static final String HEADER_MANAGER = "HLSRequest.header_manager";
private static final String COOKIE_MANAGER = "HLSRequest.cookie_manager";
Expand Down Expand Up @@ -151,6 +153,14 @@ public void setResolutionSelector(ResolutionSelector selector) {
setProperty(CUSTOM_RESOLUTION_PROPERTY_NAME, selector.getCustomResolution());
}

public Protocol getProtocolSelector() {
return Protocol.valueOf(getPropertyAsString(PROTOCOL_PROPERTY_NAME, Protocol.AUTOMATIC.name()));
}

public void setProtocolSelector(Protocol selector) {
setProperty(PROTOCOL_PROPERTY_NAME, selector.name());
}

public boolean getResumeVideoStatus() {
return this.getPropertyAsBoolean(RESUME_DOWNLOAD_PROPERTY_NAME);
}
Expand Down Expand Up @@ -189,8 +199,12 @@ public SampleResult sample() {

String url = getMasterUrl();
if (!url.equals(lastMasterUrl)) {
sampler = factory
.getVideoStreamingSampler(url, this, httpClient, timeMachine, sampleResultProcessor);
try {
sampler = factory
.getVideoStreamingSampler(url, this, httpClient, timeMachine, sampleResultProcessor);
} catch (IllegalArgumentException e) {
LOG.error("Error initializing the sampler", e);
}
} else if (!this.getResumeVideoStatus()) {
sampler.resetVideoStatus();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,20 @@
public class VideoStreamingSamplerFactory {

public VideoStreamingSampler<?, ?> getVideoStreamingSampler(String url, HlsSampler baseSampler,
VideoStreamingHttpClient httpClient,
TimeMachine timeMachine, SampleResultProcessor sampleResultProcessor) {
//HLS Master Playlist must contain this .m3u8 extension in their URLs
if (url.contains(".m3u8")) {
return createHlsSampler(baseSampler, httpClient, timeMachine, sampleResultProcessor);
} else {
return createDashSampler(baseSampler, httpClient, timeMachine, sampleResultProcessor);
VideoStreamingHttpClient httpClient, TimeMachine timeMachine,
SampleResultProcessor sampleResultProcessor) throws IllegalArgumentException {
switch (baseSampler.getProtocolSelector()) {
case HLS:
return createHlsSampler(baseSampler, httpClient, timeMachine, sampleResultProcessor);
case MPEG_DASH:
return createDashSampler(baseSampler, httpClient, timeMachine, sampleResultProcessor);
default:
//HLS Master Playlist must contain this .m3u8 extension in their URLs
if (url.contains(".m3u8")) {
return createHlsSampler(baseSampler, httpClient, timeMachine, sampleResultProcessor);
} else {
return createDashSampler(baseSampler, httpClient, timeMachine, sampleResultProcessor);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.blazemeter.jmeter.videostreaming.core;

public enum Protocol {
AUTOMATIC("Automatic"),
HLS("HLS"),
MPEG_DASH("MPEG-DASH");

private final String name;

Protocol(String name) {
this.name = name;
}

@Override
public String toString() {
return name;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ public class DashMediaSegment extends MediaSegment {

private final Duration startTime;
private final MediaPeriod period;
private final Duration presentationTimeOffset;

public DashMediaSegment(MediaPeriod period, long sequenceNumber, URI uri, Duration duration,
Duration startTime) {
Duration startTime, Duration presentationTimeOffset) {
super(sequenceNumber, uri, duration);
this.startTime = startTime;
this.period = period;
this.presentationTimeOffset = presentationTimeOffset;
}

public MediaPeriod getPeriod() {
Expand All @@ -26,7 +28,10 @@ public Duration getEndTime() {
}

public Instant getStartAvailabilityTime() {
return period.getAvailabilityStartTime().plus(getEndTime());
return period.getAvailabilityStartTime().plus(getEndTime()).minus(presentationTimeOffset);
}

public Duration getDuration() {
return duration;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -184,10 +184,12 @@ private void downloadNextSegment() throws InterruptedException {
}

private void awaitSegmentAvailable(DashMediaSegment segment) throws InterruptedException {
Instant availabilityTime = segment.getStartAvailabilityTime();
Instant now = timeMachine.now();
if (availabilityTime.isAfter(now)) {
timeMachine.awaitMillis(Duration.between(availabilityTime, now).toMillis());
Instant availabilityTime =
segment.getStartAvailabilityTime().plus(segment.getDuration());
//The clocks have to be synchronized to avoid error on segments availability
Instant nowSynchronized = timeMachine.now().plus(manifest.getClocksDiff());
if (availabilityTime.isAfter(nowSynchronized)) {
timeMachine.awaitMillis(Duration.between(nowSynchronized, availabilityTime).toMillis());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ public Duration getBufferStartTime() {
.minus(bufferTime);
}

public Duration getClocksDiff() {
OffsetDateTime publishTime = mpd.getPublishTime();
return publishTime != null ? Duration.between(downloadTime, publishTime) : Duration.ZERO;
}

public Instant getAvailabilityStartTime() {
OffsetDateTime time = mpd.getAvailabilityStartTime();
return time != null ? time.toInstant() : Instant.MIN;
Expand Down
Loading

0 comments on commit 563c8a7

Please sign in to comment.