声音控制
class
VerticalSlider:
UIControl
1
public let slider = UISlider()
class
SliderInfoView
1
2
3
var iconNames: [String] = []
let levelSlider: VerticalSlider
var levelImageView: UIImageView
class
VolumeControlView:
SliderInfoView
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
init() {
super.init(frame: .zero)
self.levelSlider.value = AVAudioSession.sharedInstance().outputVolume // 初始值
if !UIAccessibility.isVoiceOverRunning {
levelSlider.setThumbImage(image: UIImage(), for: .normal)
}
levelSlider.addTarget(self, action: #selector(self.onVolumeChange), for:
.valueChanged)
self.iconNames = ["noSound", "lowSound", "mediumSound", "highSound"]
levelSlider.accessibilityLabel = NSLocalizedString("VOLUME_SLIDER", comment: "")
levelSlider.accessibilityHint = NSLocalizedString("VOLUME_HINT", comment: "")
levelSlider.accessibilityTraits = .adjustable
}
@objc func onVolumeChange() {
MPVolumeView.setVolume(levelSlider.value)
updateIcon(level: levelSlider.value)
}
1
2
3
4
5
6
7
8
9
extension MPVolumeView {
static func setVolume(_ volume: Float) {
let volumeView = MPVolumeView()
let slider = volumeView.subviews.first(where: { $0 is UISlider }) as? UISlider
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.01) {
slider?.value = volume
}
}
}
亮度控制
class
BrightnessControlView:
SliderInfoView
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
init() {
super.init(frame: .zero)
if !UIAccessibility.isVoiceOverRunning {
levelSlider.setThumbImage(image: UIImage(), for: .normal)
}
self.iconNames = ["brightnessLow", "brightnessLow", "brightnessMedium",
"brightnessHigh"]
levelSlider.addTarget(self, action: #selector(self.onLuminosityChange), for:
.valueChanged)
levelSlider.accessibilityLabel = NSLocalizedString("BRIGHTNESS_SLIDER", comment: "")
levelSlider.accessibilityHint = NSLocalizedString("BRIGHTNESS_HINT", comment: "")
levelSlider.accessibilityTraits = .adjustable
updateIcon(level: Float(UIScreen.main.brightness)) // 初始值
}
@objc func onLuminosityChange() {
UIScreen.main.brightness = CGFloat(levelSlider.value)
updateIcon(level: levelSlider.value)
}
播放进度控制
protocol
MediaScrubProgressBarDelegate
1
func mediaScrubProgressBarShouldResetIdleTimer()
class
OBSlider:
UISlider
@property (assign, nonatomic, readonly) float scrubbingSpeed;
@property (strong, nonatomic) NSArray *scrubbingSpeeds;
@property (strong, nonatomic) NSArray *scrubbingSpeedChangePositions;
@property (assign, nonatomic, readwrite) float scrubbingSpeed;
@property (assign, nonatomic, readwrite) float realPositionValue;
@property (assign, nonatomic) CGPoint beganTrackingLocation;
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
BOOL beginTracking = [super beginTrackingWithTouch:touch withEvent:event];
if (beginTracking)
{
// Set the beginning tracking location to the centre of the current
// position of the thumb. This ensures that the thumb is correctly re-positioned
// when the touch position moves back to the track after tracking in one
// of the slower tracking zones.
CGRect thumbRect = [self thumbRectForBounds:self.bounds
trackRect:[self trackRectForBounds:self.bounds]
value:self.value];
self.beganTrackingLocation = CGPointMake(thumbRect.origin.x + thumbRect.size.width / 2.0f,
thumbRect.origin.y + thumbRect.size.height / 2.0f);
self.realPositionValue = self.value;
}
return beginTracking;
}
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
if (self.tracking)
{
CGPoint previousLocation = [touch previousLocationInView:self];
CGPoint currentLocation = [touch locationInView:self];
CGFloat trackingOffset = currentLocation.x - previousLocation.x;
// Find the scrubbing speed that curresponds to the touch's vertical offset
CGFloat verticalOffset = fabsf(currentLocation.y - self.beganTrackingLocation.y);
NSUInteger scrubbingSpeedChangePosIndex = [self indexOfLowerScrubbingSpeed:self.scrubbingSpeedChangePositions forOffset:verticalOffset];
if (scrubbingSpeedChangePosIndex == NSNotFound) {
scrubbingSpeedChangePosIndex = [self.scrubbingSpeeds count];
}
self.scrubbingSpeed = [[self.scrubbingSpeeds objectAtIndex:scrubbingSpeedChangePosIndex - 1] floatValue];
CGRect trackRect = [self trackRectForBounds:self.bounds];
self.realPositionValue = self.realPositionValue + (self.maximumValue - self.minimumValue) * (trackingOffset / trackRect.size.width);
CGFloat valueAdjustment = self.scrubbingSpeed * (self.maximumValue - self.minimumValue) * (trackingOffset / trackRect.size.width);
CGFloat thumbAdjustment = 0.0f;
if ( ((self.beganTrackingLocation.y < currentLocation.y) && (currentLocation.y < previousLocation.y)) ||
((self.beganTrackingLocation.y > currentLocation.y) && (currentLocation.y > previousLocation.y)) )
{
// We are getting closer to the slider, go closer to the real location
thumbAdjustment = (self.realPositionValue - self.value) / (1 + fabsf(currentLocation.y - self.beganTrackingLocation.y));
}
self.value += valueAdjustment + thumbAdjustment;
if (self.continuous) {
[self sendActionsForControlEvents:UIControlEventValueChanged];
}
}
return self.tracking;
}
- (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
if (self.tracking)
{
self.scrubbingSpeed = [[self.scrubbingSpeeds objectAtIndex:0] floatValue];
[self sendActionsForControlEvents:UIControlEventValueChanged];
}
}
- (NSArray *) defaultScrubbingSpeeds
{
return [NSArray arrayWithObjects:
[NSNumber numberWithFloat:1.0f],
[NSNumber numberWithFloat:0.5f],
[NSNumber numberWithFloat:0.25f],
[NSNumber numberWithFloat:0.1f],
nil];
}
- (NSArray *) defaultScrubbingSpeedChangePositions
{
return [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.0f],
[NSNumber numberWithFloat:50.0f],
[NSNumber numberWithFloat:100.0f],
[NSNumber numberWithFloat:150.0f],
nil];
}
class
VLCOBSlider:
OBSlider
class
MediaScrubProgressBar
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@objc weak var delegate: MediaScrubProgressBarDelegate?
private var playbackService = PlaybackService.sharedInstance()
private var positionSet: Bool = true
private var isScrubbing: Bool = false
@objc lazy private(set) var progressSlider: VLCOBSlider
private lazy var elapsedTimeLabel: UILabel = {
var label = UILabel()
label.font = UIFont.boldSystemFont(ofSize: 13)
label.textColor = PresentationTheme.current.colors.orangeUI
label.text = "--:--"
label.numberOfLines = 1
label.setContentHuggingPriority(.defaultLow, for: .horizontal)
return label
}()
private lazy var remainingTimeButton: UIButton = {
let remainingTimeButton = UIButton(type: .custom)
remainingTimeButton.addTarget(self,
action: #selector(handleTimeDisplay),
for: .touchUpInside)
remainingTimeButton.setTitle("--:--", for: .normal)
remainingTimeButton.setTitleColor(.white, for: .normal)
// Use a monospace variant for the digits so the width does not jitter as the
numbers changes.
remainingTimeButton.titleLabel?.font = UIFont.monospacedDigitSystemFont(ofSize: 13,
weight:
.semibold)
remainingTimeButton.setContentHuggingPriority(.defaultLow, for: .horizontal)
return remainingTimeButton
}()
其他控制
class
VideoPlayerControls
播放网络串流
class
VLCOpenNetworkStreamViewController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
- (void)_openURLStringAndDismiss:(NSString *)url
{
NSURL *playbackURL = [NSURL URLWithString:url];
NSURL *subtitlesURL = nil;
if (([playbackURL.scheme isEqualToString:@"http"] || [playbackURL.scheme isEqualToString:@"https"]) && self.ScanSubToggleSwitch.on) {
subtitlesURL = [self _checkURLofSubtitle:playbackURL];
}
VLCMedia *media = [VLCMedia mediaWithURL:[NSURL URLWithString:url]];
VLCMediaList *medialist = [[VLCMediaList alloc] init];
[medialist addMedia:media];
[[VLCPlaybackService sharedInstance] playMediaList:medialist firstIndex:0 subtitlesFilePath:subtitlesURL.absoluteString];
}
// 检查下载可能存在的字幕文件
- (NSURL *)_checkURLofSubtitle:(NSURL *)url
{
NSCharacterSet *characterFilter = [NSCharacterSet characterSetWithCharactersInString:@"\\.():$"];
NSString *subtitleFileExtensions = [[kSupportedSubtitleFileExtensions componentsSeparatedByCharactersInSet:characterFilter] componentsJoinedByString:@""];
NSArray *arraySubtitleFileExtensions = [subtitleFileExtensions componentsSeparatedByString:@"|"];
NSURL *urlWithoutExtension = [url URLByDeletingPathExtension];
NSUInteger count = arraySubtitleFileExtensions.count;
for (int i = 0; i < count; i++) {
NSURL *checkURL = [urlWithoutExtension URLByAppendingPathExtension:arraySubtitleFileExtensions[i]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:checkURL];
request.HTTPMethod = @"HEAD";
NSURLResponse *response = nil;
NSError *error = nil;
[self sendSynchronousRequest:request returningResponse:&response error:&error];
NSInteger httpStatus = [(NSHTTPURLResponse *)response statusCode];
if (httpStatus == 200) {
APLog(@"%s:found matching spu file: %@", __PRETTY_FUNCTION__, checkURL);
return checkURL;
}
}
return nil;
}
支持的字幕文件格式
#define kSupportedSubtitleFileExtensions @"\\.(cdg|idx|srt|sub|utf|ass|ssa|aqt|jss|psb|rt|smi|txt|smil|stl|usf|dks|pjs|mpl2|mks|vtt|ttml|dfxp)$"