shakebug-react-native 2.4.9 → 2.4.11
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/README.md +0 -12
- package/VideoPlayer/PlayVideo.tsx +4 -46
- package/VideoPlayer/index.js +6 -43
- package/android/src/main/java/com/shakebug/BaseUtils.java +7 -19
- package/android/src/main/java/com/shakebug/DeviceUtil.java +34 -21
- package/android/src/main/java/com/shakebug/ShakebugPackage.java +3 -3
- package/android/src/main/java/com/shakebug/UnitConverter.java +8 -8
- package/android/src/main/java/com/shakebug/VideoPlayerModule.java +81 -0
- package/android/src/main/java/com/shakebug/VideoTrimModule.java +387 -0
- package/android/src/main/java/com/shakebug/VideoTrimUtil.java +133 -0
- package/android/src/main/java/com/shakebug/adapters/VideoTrimmerAdapter.java +53 -0
- package/android/src/main/java/com/shakebug/callback/SingleCallback.java +0 -6
- package/android/src/main/java/com/shakebug/thread/BackgroundExecutor.java +1 -93
- package/android/src/main/java/com/shakebug/thread/UiThreadExecutor.java +0 -37
- package/android/src/main/java/com/shakebug/utils/StorageUtil.java +135 -0
- package/android/src/main/java/com/shakebug/utils/VideoTrimmerUtil.java +70 -0
- package/android/src/main/java/com/shakebug/widgets/RangeSeekBarView.java +534 -0
- package/android/src/main/java/com/shakebug/widgets/SpacesItemDecoration2.java +1 -2
- package/android/src/main/java/com/shakebug/widgets/VideoPlayerView.java +317 -0
- package/android/src/main/java/com/shakebug/widgets/VideoTrimmerView.java +520 -0
- package/android/src/main/res/drawable/ic_volume_off.xml +10 -0
- package/android/src/main/res/drawable/ic_volume_on.xml +10 -0
- package/android/src/main/res/layout/{video_player_view.xml → shakebug_video_player_view.xml} +29 -9
- package/ios/TrimVideo/ShakebugSDKVideoTrim.swift +389 -0
- package/ios/TrimVideo/VideoTrim-Bridging-Header.h +10 -0
- package/ios/TrimVideo/VideoTrim.m +23 -0
- package/ios/TrimVideo/VideoTrimmer.swift +810 -0
- package/ios/TrimVideo/VideoTrimmerThumb.swift +119 -0
- package/ios/TrimVideo/VideoTrimmerViewController.swift +322 -0
- package/ios/VideoPlayer/VideoPlayerModule.m +23 -7
- package/package.json +1 -1
- package/src/APIs/AddIssue.js +7 -5
- package/src/APIs/AddReview.js +5 -2
- package/src/APIs/CustomUserId.js +5 -2
- package/src/APIs/EndSession.js +3 -2
- package/src/APIs/Event.js +5 -2
- package/src/APIs/FetchRemoteCodeAPI.js +5 -2
- package/src/APIs/MatchKey.js +3 -2
- package/src/APIs/StartSession.js +44 -23
- package/src/Componanats/LoadingModal.js +59 -0
- package/src/Componanats/ShakeFunction.js +32 -14
- package/src/Screens/AddDetailsScreen.js +28 -31
- package/src/Screens/FirstScreen.js +15 -18
- package/src/Screens/Shakebug.js +11 -6
- package/src/Screens/TrimVideoScreen.js +37 -22
- package/src/index.tsx +7 -6
- package/trimVideo/src/index.tsx +12 -48
- package/Volumes:.fileloc +0 -8
- package/shakebug-react-native-2.4.8.tgz +0 -0
- /package/android/src/main/res/layout/{video_thumb_item_layout.xml → shakebug_video_thumb_item_layout.xml} +0 -0
- /package/android/src/main/res/layout/{video_trimmer_view.xml → shakebug_video_trimmer_view.xml} +0 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
//
|
|
2
|
+
// VideoTrimmerThumb.swift
|
|
3
|
+
// Shakebug
|
|
4
|
+
//
|
|
5
|
+
// Created by ideveloper2 on 26/04/24.
|
|
6
|
+
// Copyright © 2024 Facebook. All rights reserved.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
import UIKit
|
|
10
|
+
|
|
11
|
+
@available(iOS 13.0, *)
|
|
12
|
+
class VideoTrimmerThumb: UIView {
|
|
13
|
+
var isActive = false
|
|
14
|
+
|
|
15
|
+
var leadingChevronImageView = UIImageView(image: UIImage(systemName: "chevron.compact.left"))
|
|
16
|
+
var trailingChevronView = UIImageView(image: UIImage(systemName: "chevron.compact.right"))
|
|
17
|
+
|
|
18
|
+
var wrapperView = UIView()
|
|
19
|
+
var leadingView = UIView()
|
|
20
|
+
var trailingView = UIView()
|
|
21
|
+
var topView = UIView()
|
|
22
|
+
var bottomView = UIView()
|
|
23
|
+
|
|
24
|
+
let leadingGrabber = UIControl()
|
|
25
|
+
let trailingGrabber = UIControl()
|
|
26
|
+
|
|
27
|
+
let chevronWidth = CGFloat(16)
|
|
28
|
+
let edgeHeight = CGFloat(4)
|
|
29
|
+
|
|
30
|
+
// MARK: - Input
|
|
31
|
+
@objc private func x(_ sender: Any) {
|
|
32
|
+
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// MARK: - Private
|
|
36
|
+
private func updateColor() {
|
|
37
|
+
let color = UIColor.systemYellow
|
|
38
|
+
leadingView.backgroundColor = color
|
|
39
|
+
trailingView.backgroundColor = color
|
|
40
|
+
topView.backgroundColor = color
|
|
41
|
+
bottomView.backgroundColor = color
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private func setup() {
|
|
45
|
+
|
|
46
|
+
leadingChevronImageView.contentMode = .scaleAspectFill
|
|
47
|
+
trailingChevronView.contentMode = .scaleAspectFill
|
|
48
|
+
|
|
49
|
+
leadingChevronImageView.tintColor = .white
|
|
50
|
+
trailingChevronView.tintColor = .white
|
|
51
|
+
|
|
52
|
+
leadingChevronImageView.tintAdjustmentMode = .normal
|
|
53
|
+
trailingChevronView.tintAdjustmentMode = .normal
|
|
54
|
+
|
|
55
|
+
leadingView.layer.cornerRadius = 6
|
|
56
|
+
leadingView.layer.cornerCurve = .continuous
|
|
57
|
+
leadingView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMinXMinYCorner]
|
|
58
|
+
|
|
59
|
+
trailingView.layer.cornerRadius = 6
|
|
60
|
+
trailingView.layer.cornerCurve = .continuous
|
|
61
|
+
trailingView.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMaxXMinYCorner]
|
|
62
|
+
|
|
63
|
+
leadingView.addSubview(leadingChevronImageView)
|
|
64
|
+
trailingView.addSubview(trailingChevronView)
|
|
65
|
+
|
|
66
|
+
// wrapperView.layer.shadowColor = UIColor.black.cgColor
|
|
67
|
+
// wrapperView.layer.shadowOffset = .zero
|
|
68
|
+
// wrapperView.layer.shadowRadius = 2
|
|
69
|
+
// wrapperView.layer.shadowOpacity = 0.25
|
|
70
|
+
|
|
71
|
+
wrapperView.addSubview(leadingView)
|
|
72
|
+
wrapperView.addSubview(trailingView)
|
|
73
|
+
wrapperView.addSubview(topView)
|
|
74
|
+
wrapperView.addSubview(bottomView)
|
|
75
|
+
addSubview(wrapperView)
|
|
76
|
+
|
|
77
|
+
wrapperView.addSubview(leadingGrabber)
|
|
78
|
+
wrapperView.addSubview(trailingGrabber)
|
|
79
|
+
|
|
80
|
+
updateColor()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
// MARK: - UIView
|
|
86
|
+
|
|
87
|
+
override func layoutSubviews() {
|
|
88
|
+
super.layoutSubviews()
|
|
89
|
+
|
|
90
|
+
let size = bounds.size
|
|
91
|
+
|
|
92
|
+
wrapperView.frame = CGRect(origin: .zero, size: size)
|
|
93
|
+
|
|
94
|
+
leadingView.frame = CGRect(x: 0, y: 0, width: chevronWidth, height: bounds.height)
|
|
95
|
+
trailingView.frame = CGRect(x: bounds.width - chevronWidth, y: 0, width: chevronWidth, height: bounds.height)
|
|
96
|
+
topView.frame = CGRect(x: chevronWidth, y: 0, width: bounds.width - chevronWidth * 2, height: edgeHeight)
|
|
97
|
+
bottomView.frame = CGRect(x: chevronWidth, y: bounds.height - edgeHeight, width: bounds.width - chevronWidth * 2, height: edgeHeight)
|
|
98
|
+
|
|
99
|
+
let chevronHorizontalInset = CGFloat(2)
|
|
100
|
+
let chevronVerticalInset = CGFloat(8)
|
|
101
|
+
let chevronFrame = CGRect(x: chevronHorizontalInset, y: chevronVerticalInset, width: chevronWidth - chevronHorizontalInset * 2, height: size.height - chevronVerticalInset * 2)
|
|
102
|
+
|
|
103
|
+
leadingChevronImageView.frame = chevronFrame
|
|
104
|
+
trailingChevronView.frame = chevronFrame
|
|
105
|
+
|
|
106
|
+
leadingGrabber.frame = leadingView.frame
|
|
107
|
+
trailingGrabber.frame = trailingView.frame
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
override init(frame: CGRect) {
|
|
111
|
+
super.init(frame: frame)
|
|
112
|
+
setup()
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
required init?(coder: NSCoder) {
|
|
116
|
+
super.init(coder: coder)
|
|
117
|
+
setup()
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
//
|
|
2
|
+
// VideoTrimmerViewController.swift
|
|
3
|
+
// Shakebug
|
|
4
|
+
//
|
|
5
|
+
// Created by ideveloper2 on 26/04/24.
|
|
6
|
+
// Copyright © 2024 Facebook. All rights reserved.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
import UIKit
|
|
10
|
+
import AVKit
|
|
11
|
+
|
|
12
|
+
extension CMTime {
|
|
13
|
+
var displayString: String {
|
|
14
|
+
let offset = TimeInterval(seconds)
|
|
15
|
+
let numberOfNanosecondsFloat = (offset - TimeInterval(Int(offset))) * 1000.0
|
|
16
|
+
let nanoseconds = Int(numberOfNanosecondsFloat)
|
|
17
|
+
let formatter = DateComponentsFormatter()
|
|
18
|
+
formatter.unitsStyle = .positional
|
|
19
|
+
formatter.zeroFormattingBehavior = .pad
|
|
20
|
+
formatter.allowedUnits = [.minute, .second]
|
|
21
|
+
return String(format: "%@.%03d", formatter.string(from: offset) ?? "00:00", nanoseconds)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
extension AVAsset {
|
|
26
|
+
var fullRange: CMTimeRange {
|
|
27
|
+
return CMTimeRange(start: .zero, duration: duration)
|
|
28
|
+
}
|
|
29
|
+
func trimmedComposition(_ range: CMTimeRange) -> AVAsset {
|
|
30
|
+
guard CMTimeRangeEqual(fullRange, range) == false else {return self}
|
|
31
|
+
|
|
32
|
+
let composition = AVMutableComposition()
|
|
33
|
+
try? composition.insertTimeRange(range, of: self, at: .zero)
|
|
34
|
+
|
|
35
|
+
if let videoTrack = tracks(withMediaType: .video).first {
|
|
36
|
+
composition.tracks.forEach {$0.preferredTransform = videoTrack.preferredTransform}
|
|
37
|
+
}
|
|
38
|
+
return composition
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@available(iOS 13.0, *)
|
|
43
|
+
class VideoTrimmerViewController: UIViewController {
|
|
44
|
+
var asset: AVAsset!
|
|
45
|
+
var maximumDuration: Int?
|
|
46
|
+
var minimumDuration: Int?
|
|
47
|
+
var cancelBtnText = "Cancel"
|
|
48
|
+
var saveButtonText = "Save"
|
|
49
|
+
var cancelBtnClicked: (() -> Void)?
|
|
50
|
+
var saveBtnClicked: ((CMTimeRange) -> Void)?
|
|
51
|
+
|
|
52
|
+
let playerController = AVPlayerViewController()
|
|
53
|
+
var trimmer: VideoTrimmer!
|
|
54
|
+
var timingStackView: UIStackView!
|
|
55
|
+
var leadingTrimLabel: UILabel!
|
|
56
|
+
var currentTimeLabel: UILabel!
|
|
57
|
+
var trailingTrimLabel: UILabel!
|
|
58
|
+
|
|
59
|
+
private var btnStackView: UIStackView!
|
|
60
|
+
private var cancelBtn: UIButton!
|
|
61
|
+
private var playBtn: UIButton!
|
|
62
|
+
private var saveBtn: UIButton!
|
|
63
|
+
private let playIcon = UIImage(systemName: "play.fill")
|
|
64
|
+
private let pauseIcon = UIImage(systemName: "pause.fill")
|
|
65
|
+
|
|
66
|
+
private var wasPlaying = false
|
|
67
|
+
private var player: AVPlayer! {playerController.player}
|
|
68
|
+
private var timeObserverToken: Any?
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
// MARK: - Input
|
|
72
|
+
@objc private func didBeginTrimming(_ sender: VideoTrimmer) {
|
|
73
|
+
updateLabels()
|
|
74
|
+
|
|
75
|
+
wasPlaying = (player.timeControlStatus != .paused)
|
|
76
|
+
player.pause()
|
|
77
|
+
|
|
78
|
+
updatePlayerAsset()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@objc private func didEndTrimming(_ sender: VideoTrimmer) {
|
|
82
|
+
updateLabels()
|
|
83
|
+
|
|
84
|
+
updatePlayerAsset()
|
|
85
|
+
|
|
86
|
+
if wasPlaying == true {
|
|
87
|
+
player.play()
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@objc private func selectedRangeDidChanged(_ sender: VideoTrimmer) {
|
|
92
|
+
updateLabels()
|
|
93
|
+
if trimmer.trimmingState == .leading {
|
|
94
|
+
player.seek(to: trimmer.selectedRange.start, toleranceBefore: .zero, toleranceAfter: .zero)
|
|
95
|
+
} else if trimmer.trimmingState == .trailing {
|
|
96
|
+
player.seek(to: trimmer.selectedRange.end, toleranceBefore: .zero, toleranceAfter: .zero)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@objc private func didBeginScrubbing(_ sender: VideoTrimmer) {
|
|
101
|
+
updateLabels()
|
|
102
|
+
|
|
103
|
+
wasPlaying = (player.timeControlStatus != .paused)
|
|
104
|
+
player.pause()
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
@objc private func didEndScrubbing(_ sender: VideoTrimmer) {
|
|
108
|
+
updateLabels()
|
|
109
|
+
|
|
110
|
+
if wasPlaying == true {
|
|
111
|
+
player.play()
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
@objc private func progressDidChanged(_ sender: VideoTrimmer) {
|
|
116
|
+
updateLabels()
|
|
117
|
+
|
|
118
|
+
player.seek(to: trimmer.progress, toleranceBefore: .zero, toleranceAfter: .zero)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// MARK: - Private
|
|
122
|
+
private func updateLabels() {
|
|
123
|
+
leadingTrimLabel.text = trimmer.selectedRange.start.displayString
|
|
124
|
+
currentTimeLabel.text = trimmer.progress.displayString
|
|
125
|
+
trailingTrimLabel.text = trimmer.selectedRange.end.displayString
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private func updatePlayerAsset() {
|
|
129
|
+
if player.currentItem == nil {
|
|
130
|
+
let item = AVPlayerItem(asset: asset)
|
|
131
|
+
player.replaceCurrentItem(with: item)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if trimmer.trimmingState == .none {
|
|
135
|
+
player.currentItem?.forwardPlaybackEndTime = trimmer.selectedRange.end
|
|
136
|
+
|
|
137
|
+
let currentTime = player.currentTime().seconds
|
|
138
|
+
let endTime = trimmer.selectedRange.end.seconds
|
|
139
|
+
let startTime = trimmer.selectedRange.start.seconds
|
|
140
|
+
|
|
141
|
+
if currentTime < startTime || currentTime >= endTime - 0.05 {
|
|
142
|
+
player.seek(to: trimmer.selectedRange.start, toleranceBefore: .zero, toleranceAfter: .zero)
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
player.currentItem?.forwardPlaybackEndTime = .invalid
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// MARK: - UIViewController
|
|
150
|
+
override func viewDidLoad() {
|
|
151
|
+
super.viewDidLoad()
|
|
152
|
+
|
|
153
|
+
view.backgroundColor = .black
|
|
154
|
+
|
|
155
|
+
// bottom action buttons
|
|
156
|
+
cancelBtn = UIButton(type: .system)
|
|
157
|
+
cancelBtn.setTitle(cancelBtnText, for: .normal)
|
|
158
|
+
cancelBtn.titleLabel?.font = .systemFont(ofSize: 18)
|
|
159
|
+
cancelBtn.setTitleColor(.white, for: .normal)
|
|
160
|
+
cancelBtn.addTarget(self, action: #selector(onCancelBtnClicked), for: .touchUpInside)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
playBtn = UIButton(type: .system)
|
|
164
|
+
playBtn.setImage(playIcon, for: .normal)
|
|
165
|
+
playBtn.tintColor = .systemBlue
|
|
166
|
+
playBtn.addTarget(self, action: #selector(togglePlay(sender:)), for: .touchUpInside)
|
|
167
|
+
|
|
168
|
+
saveBtn = UIButton(type: .system)
|
|
169
|
+
saveBtn.setTitle(saveButtonText, for: .normal)
|
|
170
|
+
saveBtn.titleLabel?.font = .systemFont(ofSize: 18)
|
|
171
|
+
saveBtn.setTitleColor(.systemBlue, for: .normal)
|
|
172
|
+
saveBtn.addTarget(self, action: #selector(onSaveBtnClicked), for: .touchUpInside)
|
|
173
|
+
|
|
174
|
+
btnStackView = UIStackView(arrangedSubviews: [cancelBtn, playBtn, saveBtn])
|
|
175
|
+
btnStackView.axis = .horizontal
|
|
176
|
+
btnStackView.alignment = .fill
|
|
177
|
+
btnStackView.distribution = .fillEqually
|
|
178
|
+
btnStackView.spacing = UIStackView.spacingUseSystem
|
|
179
|
+
view.addSubview(btnStackView)
|
|
180
|
+
btnStackView.translatesAutoresizingMaskIntoConstraints = false
|
|
181
|
+
NSLayoutConstraint.activate([
|
|
182
|
+
btnStackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16),
|
|
183
|
+
btnStackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16),
|
|
184
|
+
btnStackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -16),
|
|
185
|
+
])
|
|
186
|
+
|
|
187
|
+
// time labels
|
|
188
|
+
leadingTrimLabel = UILabel()
|
|
189
|
+
leadingTrimLabel.font = UIFont.preferredFont(forTextStyle: .caption1)
|
|
190
|
+
leadingTrimLabel.textAlignment = .left
|
|
191
|
+
leadingTrimLabel.textColor = .white
|
|
192
|
+
|
|
193
|
+
currentTimeLabel = UILabel()
|
|
194
|
+
currentTimeLabel.font = UIFont.preferredFont(forTextStyle: .caption1)
|
|
195
|
+
currentTimeLabel.textAlignment = .center
|
|
196
|
+
currentTimeLabel.textColor = .white
|
|
197
|
+
|
|
198
|
+
trailingTrimLabel = UILabel()
|
|
199
|
+
trailingTrimLabel.font = UIFont.preferredFont(forTextStyle: .caption1)
|
|
200
|
+
trailingTrimLabel.textAlignment = .right
|
|
201
|
+
trailingTrimLabel.textColor = .white
|
|
202
|
+
|
|
203
|
+
timingStackView = UIStackView(arrangedSubviews: [leadingTrimLabel, currentTimeLabel, trailingTrimLabel])
|
|
204
|
+
timingStackView.axis = .horizontal
|
|
205
|
+
timingStackView.alignment = .fill
|
|
206
|
+
timingStackView.distribution = .fillEqually
|
|
207
|
+
timingStackView.spacing = UIStackView.spacingUseSystem
|
|
208
|
+
view.addSubview(timingStackView)
|
|
209
|
+
timingStackView.translatesAutoresizingMaskIntoConstraints = false
|
|
210
|
+
NSLayoutConstraint.activate([
|
|
211
|
+
timingStackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16),
|
|
212
|
+
timingStackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16),
|
|
213
|
+
timingStackView.bottomAnchor.constraint(equalTo: btnStackView.topAnchor, constant: -8),
|
|
214
|
+
])
|
|
215
|
+
|
|
216
|
+
// THIS IS WHERE WE SETUP THE VIDEOTRIMMER:
|
|
217
|
+
trimmer = VideoTrimmer()
|
|
218
|
+
trimmer.asset = asset // this should happen before trimmer.selectedRange below otherwise its didSet will override
|
|
219
|
+
trimmer.minimumDuration = CMTime(seconds: 1, preferredTimescale: 600)
|
|
220
|
+
|
|
221
|
+
if maximumDuration != nil {
|
|
222
|
+
trimmer.maximumDuration = CMTime(seconds: max(1, Double(maximumDuration!)), preferredTimescale: 600) // minimum 1 second
|
|
223
|
+
|
|
224
|
+
// guard check to make sure max duration can only <= asset.duration
|
|
225
|
+
if CMTimeCompare(trimmer.maximumDuration, asset.duration) == 1 {
|
|
226
|
+
trimmer.maximumDuration = asset.duration
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
trimmer.selectedRange = CMTimeRange(start: .zero, end: trimmer.maximumDuration)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if minimumDuration != nil {
|
|
233
|
+
trimmer.minimumDuration = CMTime(seconds: max(1, Double(minimumDuration!)), preferredTimescale: 600) // minimum 1 second
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
trimmer.addTarget(self, action: #selector(didBeginTrimming(_:)), for: VideoTrimmer.didBeginTrimming)
|
|
237
|
+
trimmer.addTarget(self, action: #selector(didEndTrimming(_:)), for: VideoTrimmer.didEndTrimming)
|
|
238
|
+
trimmer.addTarget(self, action: #selector(selectedRangeDidChanged(_:)), for: VideoTrimmer.selectedRangeChanged)
|
|
239
|
+
trimmer.addTarget(self, action: #selector(didBeginScrubbing(_:)), for: VideoTrimmer.didBeginScrubbing)
|
|
240
|
+
trimmer.addTarget(self, action: #selector(didEndScrubbing(_:)), for: VideoTrimmer.didEndScrubbing)
|
|
241
|
+
trimmer.addTarget(self, action: #selector(progressDidChanged(_:)), for: VideoTrimmer.progressChanged)
|
|
242
|
+
view.addSubview(trimmer)
|
|
243
|
+
trimmer.translatesAutoresizingMaskIntoConstraints = false
|
|
244
|
+
NSLayoutConstraint.activate([
|
|
245
|
+
trimmer.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
|
|
246
|
+
trimmer.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
|
|
247
|
+
trimmer.bottomAnchor.constraint(equalTo: timingStackView.topAnchor, constant: -16),
|
|
248
|
+
trimmer.heightAnchor.constraint(equalToConstant: 50),
|
|
249
|
+
])
|
|
250
|
+
|
|
251
|
+
playerController.showsPlaybackControls = false // hide control buttons
|
|
252
|
+
if #available(iOS 16.0, *) {
|
|
253
|
+
playerController.allowsVideoFrameAnalysis = false // hide live text
|
|
254
|
+
}
|
|
255
|
+
playerController.player = AVPlayer()
|
|
256
|
+
try? AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: []) // this is to play audio even when device is in silent mode
|
|
257
|
+
addChild(playerController)
|
|
258
|
+
view.addSubview(playerController.view)
|
|
259
|
+
playerController.view.translatesAutoresizingMaskIntoConstraints = false
|
|
260
|
+
NSLayoutConstraint.activate([
|
|
261
|
+
playerController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
|
262
|
+
playerController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
|
263
|
+
playerController.view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
|
|
264
|
+
playerController.view.bottomAnchor.constraint(equalTo: trimmer.topAnchor, constant: -16)
|
|
265
|
+
])
|
|
266
|
+
|
|
267
|
+
updatePlayerAsset()
|
|
268
|
+
|
|
269
|
+
timeObserverToken = player.addPeriodicTimeObserver(forInterval: CMTime(value: 1, timescale: 30), queue: .main) { [weak self] time in
|
|
270
|
+
guard let self = self else {return}
|
|
271
|
+
|
|
272
|
+
self.currentTimeLabel.text = time.displayString
|
|
273
|
+
self.trimmer.progress = time
|
|
274
|
+
|
|
275
|
+
if self.player.timeControlStatus == .playing {
|
|
276
|
+
self.playBtn.setImage(self.pauseIcon, for: .normal)
|
|
277
|
+
} else {
|
|
278
|
+
self.playBtn.setImage(self.playIcon, for: .normal)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
updateLabels()
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
override func viewWillDisappear(_ animated: Bool) {
|
|
286
|
+
super.viewWillDisappear(animated)
|
|
287
|
+
|
|
288
|
+
player.pause()
|
|
289
|
+
if timeObserverToken != nil {
|
|
290
|
+
player.removeTimeObserver(timeObserverToken as Any)
|
|
291
|
+
timeObserverToken = nil
|
|
292
|
+
}
|
|
293
|
+
playerController.player = nil
|
|
294
|
+
playerController.dismiss(animated: false, completion: nil)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
@objc private func togglePlay(sender: UIButton) {
|
|
298
|
+
if player.timeControlStatus == .playing {
|
|
299
|
+
player.pause()
|
|
300
|
+
} else {
|
|
301
|
+
let currentTime = player.currentTime().seconds
|
|
302
|
+
let endTime = trimmer.selectedRange.end.seconds
|
|
303
|
+
let startTime = trimmer.selectedRange.start.seconds
|
|
304
|
+
|
|
305
|
+
if currentTime >= endTime - 0.05 || currentTime < startTime {
|
|
306
|
+
player.seek(to: trimmer.selectedRange.start, toleranceBefore: .zero, toleranceAfter: .zero) { _ in
|
|
307
|
+
self.player.play()
|
|
308
|
+
}
|
|
309
|
+
} else {
|
|
310
|
+
player.play()
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
@objc func onSaveBtnClicked() {
|
|
316
|
+
saveBtnClicked?(trimmer.selectedRange)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
@objc func onCancelBtnClicked() {
|
|
320
|
+
cancelBtnClicked?()
|
|
321
|
+
}
|
|
322
|
+
}
|
|
@@ -2,24 +2,40 @@
|
|
|
2
2
|
// VideoPlayerModule.m
|
|
3
3
|
// Shakebug
|
|
4
4
|
//
|
|
5
|
-
// Created by ideveloper2 on 23/04/24.
|
|
6
|
-
// Copyright © 2024 Facebook. All rights reserved.
|
|
7
|
-
//
|
|
8
5
|
|
|
9
6
|
#import "VideoPlayerModule.h"
|
|
10
7
|
#import <React/RCTLog.h>
|
|
8
|
+
#import <React/RCTUtils.h>
|
|
11
9
|
|
|
12
10
|
@implementation ShakebugSDKVideoPlayerModule
|
|
13
11
|
|
|
14
|
-
RCT_EXPORT_MODULE();
|
|
12
|
+
RCT_EXPORT_MODULE(ShakebugSDKVideoPlayerModule);
|
|
13
|
+
|
|
14
|
+
+ (BOOL)requiresMainQueueSetup {
|
|
15
|
+
return YES;
|
|
16
|
+
}
|
|
15
17
|
|
|
16
18
|
RCT_EXPORT_METHOD(playVideo:(NSString *)videoURLString) {
|
|
17
|
-
|
|
19
|
+
if (!videoURLString || videoURLString.length == 0) {
|
|
20
|
+
RCTLogError(@"Invalid video URL");
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
NSURL *videoURL;
|
|
25
|
+
if ([videoURLString hasPrefix:@"http://"] || [videoURLString hasPrefix:@"https://"]) {
|
|
26
|
+
videoURL = [NSURL URLWithString:videoURLString];
|
|
27
|
+
} else {
|
|
28
|
+
NSString *cleanPath = [videoURLString stringByReplacingOccurrencesOfString:@"file://" withString:@""];
|
|
29
|
+
videoURL = [NSURL fileURLWithPath:cleanPath];
|
|
30
|
+
}
|
|
31
|
+
|
|
18
32
|
if (videoURL) {
|
|
19
33
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
20
34
|
ShakebugSDKVideoPlayerViewController *videoPlayerVC = [[ShakebugSDKVideoPlayerViewController alloc] initWithVideoURL:videoURL];
|
|
21
|
-
UIViewController *rootViewController =
|
|
22
|
-
|
|
35
|
+
UIViewController *rootViewController = RCTPresentedViewController();
|
|
36
|
+
if (rootViewController) {
|
|
37
|
+
[rootViewController presentViewController:videoPlayerVC animated:YES completion:nil];
|
|
38
|
+
}
|
|
23
39
|
});
|
|
24
40
|
} else {
|
|
25
41
|
RCTLogError(@"Invalid video URL");
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shakebug-react-native",
|
|
3
3
|
"title": "shakebug-react-native",
|
|
4
|
-
"version": "2.4.
|
|
4
|
+
"version": "2.4.11",
|
|
5
5
|
"description": "Shakebug allows users to report the bug from your mobile phone for your application. Here users just need to shake their mobile and all the data regarding bugs & crashes can be seen by developers through their log in panel easily",
|
|
6
6
|
"main": "src/index.tsx",
|
|
7
7
|
"types": "src/index.tsx",
|
package/src/APIs/AddIssue.js
CHANGED
|
@@ -2,10 +2,11 @@ import { NativeModules, Platform } from "react-native";
|
|
|
2
2
|
import DeviceInfo from "../../DeviceInfo";
|
|
3
3
|
import NetInfo from "../../NetInfo/src";
|
|
4
4
|
|
|
5
|
-
const BASE_URL = global.ShakebugSDKEndPointURL;
|
|
6
|
-
const API_KEY = global.ShakebugSDKEndPointURLheaderKey;
|
|
7
5
|
|
|
8
6
|
async function AddIssueApi(session_id, application_id, path, isVideo, desc, email) {
|
|
7
|
+
const BASE_URL = global.ShakebugSDKEndPointURL || "https://www.shakebug.com/api/v1.6/webservice.php/";
|
|
8
|
+
const API_KEY = global.ShakebugSDKEndPointURLheaderKey || "MarchShake2022";
|
|
9
|
+
|
|
9
10
|
try {
|
|
10
11
|
|
|
11
12
|
const { ShakebugSDKSharedPreferencesModule } = NativeModules;
|
|
@@ -30,7 +31,7 @@ async function AddIssueApi(session_id, application_id, path, isVideo, desc, emai
|
|
|
30
31
|
let totalMemory = await DeviceInfo.getTotalMemory();
|
|
31
32
|
let usedMemory = await DeviceInfo.getUsedMemory();
|
|
32
33
|
let memoryInBytes = totalMemory - usedMemory;
|
|
33
|
-
|
|
34
|
+
|
|
34
35
|
let memory = "";
|
|
35
36
|
if (memoryInBytes >= 1024 * 1024 * 1024) {
|
|
36
37
|
memory = (memoryInBytes / (1024 * 1024 * 1024)).toFixed(2) + " GB";
|
|
@@ -50,7 +51,7 @@ async function AddIssueApi(session_id, application_id, path, isVideo, desc, emai
|
|
|
50
51
|
formData.append("applicationsid", application_id);
|
|
51
52
|
formData.append("session_id", session_id);
|
|
52
53
|
formData.append("batteryLevel", batteryLevel);
|
|
53
|
-
formData.append("image", {uri: media, name: name, type: type});
|
|
54
|
+
formData.append("image", { uri: media, name: name, type: type });
|
|
54
55
|
formData.append("memory", memory);
|
|
55
56
|
formData.append("email", email);
|
|
56
57
|
formData.append("devicemodel", `${architecture[0]} -- ${model}`);
|
|
@@ -63,7 +64,8 @@ async function AddIssueApi(session_id, application_id, path, isVideo, desc, emai
|
|
|
63
64
|
formData.append("custom_user_id", Custom_User_Id || "")
|
|
64
65
|
formData.append("sdk_version", global.sdk_version)
|
|
65
66
|
formData.append("platform", Platform.OS)
|
|
66
|
-
|
|
67
|
+
formData.append("sdk_platform", global.sdk_platform)
|
|
68
|
+
formData.append("sdk_platform_version", global.sdk_platform_version)
|
|
67
69
|
|
|
68
70
|
const response = await fetch(BASE_URL, {
|
|
69
71
|
method: 'POST',
|
package/src/APIs/AddReview.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { NativeModules } from "react-native";
|
|
2
2
|
|
|
3
|
-
const BASE_URL = global.ShakebugSDKEndPointURL;
|
|
4
|
-
const API_KEY = global.ShakebugSDKEndPointURLheaderKey;
|
|
5
3
|
|
|
6
4
|
async function AddReviewApi(rating, review, applicationsid, session_id) {
|
|
5
|
+
const BASE_URL = global.ShakebugSDKEndPointURL || "https://www.shakebug.com/api/v1.6/webservice.php/";
|
|
6
|
+
const API_KEY = global.ShakebugSDKEndPointURLheaderKey || "MarchShake2022";
|
|
7
|
+
|
|
7
8
|
|
|
8
9
|
const { ShakebugSDKSharedPreferencesModule } = NativeModules;
|
|
9
10
|
|
|
@@ -18,6 +19,8 @@ async function AddReviewApi(rating, review, applicationsid, session_id) {
|
|
|
18
19
|
formData.append("session_id", session_id);
|
|
19
20
|
formData.append("appVersion", global.sdk_version);
|
|
20
21
|
formData.append("custom_user_id", Custom_User_Id || "");
|
|
22
|
+
formData.append("sdk_platform", global.sdk_platform)
|
|
23
|
+
formData.append("sdk_platform_version", global.sdk_platform_version)
|
|
21
24
|
|
|
22
25
|
const response = await fetch(BASE_URL, {
|
|
23
26
|
method: 'POST',
|
package/src/APIs/CustomUserId.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { NativeModules } from "react-native";
|
|
2
2
|
|
|
3
|
-
const BASE_URL = global.ShakebugSDKEndPointURL;
|
|
4
|
-
const API_KEY = global.ShakebugSDKEndPointURLheaderKey;
|
|
5
3
|
|
|
6
4
|
async function CustomUserIdApi(id, name, email, country, other, applicationsid) {
|
|
5
|
+
const BASE_URL = global.ShakebugSDKEndPointURL || "https://www.shakebug.com/api/v1.6/webservice.php/";
|
|
6
|
+
const API_KEY = global.ShakebugSDKEndPointURLheaderKey || "MarchShake2022";
|
|
7
|
+
|
|
7
8
|
|
|
8
9
|
const { ShakebugSDKSharedPreferencesModule } = NativeModules;
|
|
9
10
|
|
|
@@ -19,6 +20,8 @@ async function CustomUserIdApi(id, name, email, country, other, applicationsid)
|
|
|
19
20
|
formData.append("country", country?.trim());
|
|
20
21
|
formData.append("other", other);
|
|
21
22
|
formData.append("applicationsid", applicationsid);
|
|
23
|
+
formData.append("sdk_platform", global.sdk_platform)
|
|
24
|
+
formData.append("sdk_platform_version", global.sdk_platform_version)
|
|
22
25
|
|
|
23
26
|
const response = await fetch(BASE_URL, {
|
|
24
27
|
method: 'POST',
|
package/src/APIs/EndSession.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
const BASE_URL = global.ShakebugSDKEndPointURL;
|
|
2
|
-
const API_KEY = global.ShakebugSDKEndPointURLheaderKey;
|
|
3
1
|
|
|
4
2
|
async function SessionEndApi(session_id) {
|
|
3
|
+
const BASE_URL = global.ShakebugSDKEndPointURL || "https://www.shakebug.com/api/v1.6/webservice.php/";
|
|
4
|
+
const API_KEY = global.ShakebugSDKEndPointURLheaderKey || "MarchShake2022";
|
|
5
|
+
|
|
5
6
|
try {
|
|
6
7
|
|
|
7
8
|
const formData = new FormData();
|
package/src/APIs/Event.js
CHANGED
|
@@ -2,10 +2,11 @@ import { NativeModules } from "react-native";
|
|
|
2
2
|
import DeviceInfo from "../../DeviceInfo";
|
|
3
3
|
import NetInfo from "../../NetInfo/src";
|
|
4
4
|
|
|
5
|
-
const BASE_URL = global.ShakebugSDKEndPointURL;
|
|
6
|
-
const API_KEY = global.ShakebugSDKEndPointURLheaderKey;
|
|
7
5
|
|
|
8
6
|
async function EventApi(application_id, name, value, session_id) {
|
|
7
|
+
const BASE_URL = global.ShakebugSDKEndPointURL || "https://www.shakebug.com/api/v1.6/webservice.php/";
|
|
8
|
+
const API_KEY = global.ShakebugSDKEndPointURLheaderKey || "MarchShake2022";
|
|
9
|
+
|
|
9
10
|
try {
|
|
10
11
|
|
|
11
12
|
const { ShakebugSDKSharedPreferencesModule } = NativeModules;
|
|
@@ -20,6 +21,8 @@ async function EventApi(application_id, name, value, session_id) {
|
|
|
20
21
|
formData.append("session_id", session_id);
|
|
21
22
|
formData.append("custom_user_id", Custom_User_Id || "")
|
|
22
23
|
formData.append("sdk_version", global.sdk_version)
|
|
24
|
+
formData.append("sdk_platform", global.sdk_platform)
|
|
25
|
+
formData.append("sdk_platform_version", global.sdk_platform_version)
|
|
23
26
|
|
|
24
27
|
const response = await fetch(BASE_URL, {
|
|
25
28
|
method: 'POST',
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { NativeModules } from "react-native";
|
|
2
2
|
import DeviceInfo from "../../DeviceInfo";
|
|
3
3
|
|
|
4
|
-
const BASE_URL = global.ShakebugSDKEndPointURL;
|
|
5
|
-
const API_KEY = global.ShakebugSDKEndPointURLheaderKey;
|
|
6
4
|
|
|
7
5
|
async function FetchRemoteCodeAPI(config_key, applicationsid, session_id) {
|
|
6
|
+
const BASE_URL = global.ShakebugSDKEndPointURL || "https://www.shakebug.com/api/v1.6/webservice.php/";
|
|
7
|
+
const API_KEY = global.ShakebugSDKEndPointURLheaderKey || "MarchShake2022";
|
|
8
|
+
|
|
8
9
|
|
|
9
10
|
try {
|
|
10
11
|
const { ShakebugSDKSharedPreferencesModule } = NativeModules;
|
|
@@ -20,6 +21,8 @@ async function FetchRemoteCodeAPI(config_key, applicationsid, session_id) {
|
|
|
20
21
|
formData.append("appVersion", DeviceInfo?.getVersion());
|
|
21
22
|
formData.append("session_id", session_id);
|
|
22
23
|
formData.append("custom_user_id", Custom_User_Id || "");
|
|
24
|
+
formData.append("sdk_platform", global.sdk_platform)
|
|
25
|
+
formData.append("sdk_platform_version", global.sdk_platform_version)
|
|
23
26
|
|
|
24
27
|
const response = await fetch(BASE_URL, {
|
|
25
28
|
method: "POST",
|
package/src/APIs/MatchKey.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
const BASE_URL = global.ShakebugSDKEndPointURL;
|
|
2
|
-
const API_KEY = global.ShakebugSDKEndPointURLheaderKey;
|
|
3
1
|
|
|
4
2
|
async function MatchKeyApi(appkey, BundleId) {
|
|
3
|
+
const BASE_URL = global.ShakebugSDKEndPointURL || "https://www.shakebug.com/api/v1.6/webservice.php/";
|
|
4
|
+
const API_KEY = global.ShakebugSDKEndPointURLheaderKey || "MarchShake2022";
|
|
5
|
+
|
|
5
6
|
|
|
6
7
|
const formData = new FormData();
|
|
7
8
|
formData.append("method", "matchKeys")
|